Исходный текст: Chapter 16 Boolean search (http://greenteapress.com/thinkdast/html/thinkdast017.html).
Переводчик: Летающий дракон (https://github.com/wizardforcel).
Соглашение: CC BY-NC-SA 4.0 (http://creativecommons.org/licenses/by-nc-sa/4.0/).
С гордостью использую Google Translate.
В этой главе я показываю решение предыдущего упражнения. Затем вы будете писать код для объединения результатов поиска и их сортировки в соответствии с релевантностью по отношению к поисковым словам.
Сначала мы решаем предыдущее упражнение. Я предоставляю план для класса WikiCrawler, ваша задача — заполнить метод crawl. В качестве напоминания, вот поля класса WikiCrawler:
public class WikiCrawler {
// keeps track of where we started
private final String source;
// the index where the results go
private JedisIndex index;
// queue of URLs to be indexed
private Queue<String> queue = new LinkedList<String>();
// fetcher used to get pages from Wikipedia
final static WikiFetcher wf = new WikiFetcher();
}
Когда мы создаём экземпляр WikiCrawler, мы передаём ему source и index. Первоначально очередь queue содержит только один элемент — source.
Обратите внимание, что реализация очереди queue — это LinkedList, поэтому мы можем добавлять элементы в конец и удалять их с начала за постоянное время. Назначая объект LinkedList переменной Queue, мы ограничиваем использование методов интерфейсом Queue; конкретно, мы будем использовать offer для добавления элементов и poll для их удаления.
Вот моя реализация WikiCrawler.crawl:
public String crawl(boolean testing) throws IOException {
if (queue.isEmpty()) {
return null;
}
String url = queue.poll();
System.out.println("Crawling " + url);
if (testing==false && index.isIndexed(url)) {
System.out.println("Already indexed.");
return null;
}
Elements paragraphs;
if (testing) {
paragraphs = wf.readWikipedia(url);
} else {
paragraphs = wf.fetchWikipedia(url);
}
index.indexPage(url, paragraphs);
queueInternalLinks(paragraphs);
return url;
}
Большая часть сложности этого метода связана с его тестируемостью. Вот его логика:
Я показал реализацию Index.indexPage в разделе 15.1. Поэтому единственным новым методом является WikiCrawler.queueInternalLinks.
Я написал две версии этого метода с разными параметрами: одна принимает объект Elements, содержащий DOM-дерево каждого абзаца, а другая принимает объект Element, содержащий большинство абзацев.
Первая версия просто перебирает абзацы. Вторая версия — это фактическая логика.
void queueInternalLinks(Elements paragraphs) {
for (Element paragraph: paragraphs) {
queueInternalLinks(paragraph);
}
}
private void queueInternalLinks(Element paragraph) {
Elements elts = paragraph.select("a[href]");
for (Element elt: elts) {
String relURL = elt.attr("href");
if (relURL.startsWith("/wiki/")) {
String absURL = elt.attr("abs:href");
queue.offer(absURL);
}
}
}
Чтобы определить, является ли ссылка «внутренней» ссылкой, мы проверяем, начинается ли URL с /wiki/. Это может включать некоторые страницы, которые мы не хотим индексировать, такие как метастраницы о Википедии. Это также может исключать некоторые страницы, которые мы хотим, например, ссылки на страницы на других языках. Однако этот простой тест достаточен для начала.
Это всё. В этом упражнении не так много нового материала; это в основном возможность собрать эти произведения вместе.
Следующий этап этого проекта — реализовать инструмент поиска. Нам нужны следующие части:
Общий термин для такого процесса — «поиск информации», вы можете прочитать больше об этом на thinkdast.com/infret.
Здесь мы сосредоточимся на шагах 3 и 4. Мы уже создали простую версию шага 2. Если вы заинтересованы в создании веб-приложения, вы можете подумать о завершении шага 1.
Большинство поисковых систем могут выполнять «булевский поиск», что означает, что вы можете использовать булевскую логику для комбинирования результатов из нескольких поисковых слов. Например:
Выражение, которое включает поисковые слова и операторы, называется «запросом».
При применении к результатам поиска булевы операторы +, OR и - соответствуют операциям пересечения, объединения и разности множеств. Например, предположим, что:
В этом случае:
В следующем разделе вы напишете методы для реализации этих операций.
В репозитории этой книги вы найдёте исходный файл для этого упражнения:
Вы также найдёте некоторые вспомогательные классы, которые мы использовали в предыдущих упражнениях.
Вот начало определения класса WikiSearch:
public class WikiSearch {
// map from URLs that contain the term(s) to relevance score
private Map<String, Integer> map;
public WikiSearch(Map<String, Integer> map) {
this.map = map;
}
public Integer getRelevance(String url) { Данный текст написан на английском языке.
Integer relevance = map.get(url);
return relevance==null ? 0: relevance;
}
WikiSearch объект содержит сопоставление URL с их релевантностью. В контексте поиска информации «релевантность» используется для обозначения того, насколько страница соответствует потребностям пользователя, выведенным из запроса. Существует множество способов построения релевантности, но большинство из них основаны на «частоте поисковых слов», которая представляет собой количество показов поискового слова на странице. Распространенная мера релевантности называется TF-IDF, что означает «частота поисковых слов — обратная частота документов». Вы можете прочитать больше на сайте thinkdast.com/tfidf.
Вы можете выбрать реализацию TF-IDF позже, но мы начнем с более простого TF:
Теперь вы готовы начать практиковаться. Запустите ant build для компиляции исходных файлов, а затем запустите ant WikiSearchTest. Как обычно, он должен завершиться ошибкой, потому что у вас есть работа.
В WikiSearch.java заполните тела and, or и minus, чтобы соответствующие тесты прошли успешно. Вам не нужно беспокоиться о testSort.
Вы можете запустить WikiSearchTest без использования Jedis, поскольку он не зависит от индекса в базе данных Redis. Однако, если вы хотите выполнить запросы к индексу, вам необходимо предоставить информацию о сервере Redis в файле. См. раздел 14.3.
Запустите ant JedisMaker, чтобы убедиться, что он настроен для подключения к вашему серверу Redis. Затем запустите WikiSearch, который выводит результаты трех запросов:
Первоначальные результаты не упорядочены по определенному порядку, так как sort в WikiSearch не завершен.
Заполните тело sort, чтобы результаты возвращались в порядке возрастания релевантности. Я рекомендую использовать метод sort класса java.util.Collections, который может сортировать любой тип List. Вы можете ознакомиться с документацией на сайте thinkdast.com/collections.
Существует две версии sort:
Если вы не знакомы с интерфейсами Comparable и Comparator, я объясню их в следующем разделе.
Репозиторий этой книги содержит Card.java, который демонстрирует два способа сортировки списка объектов Card. Вот начало определения класса:
public class Card implements Comparable<Card> {
private final int rank;
private final int suit;
public Card(int rank, int suit) {
this.rank = rank;
this.suit = suit;
}
Объект Card имеет два целочисленных поля, rank и suit. Card реализует Comparable, что означает, что он предоставляет compareTo:
public int compareTo(Card that) {
if (this.suit < that.suit) {
return -1;
}
if (this.suit > that.suit) {
return 1;
}
if (this.rank < that.rank) {
return -1;
}
if (this.rank > that.rank) {
return 1;
}
return 0;
}
Спецификация compareTo указывает, что если this меньше than, следует вернуть отрицательное число, если оно больше, то положительное число, а если они равны, то 0.
При использовании однопараметрической версии Collections.sort она будет использовать предоставленный элементами метод compareTo для их сортировки. Чтобы продемонстрировать это, мы можем составить список из 52 карт следующим образом:
public static List<Card> makeDeck() {
List<Card> cards = new ArrayList<Card>();
for (int suit = 0; suit <= 3; suit++) {
for (int rank = 1; rank <= 13; rank++) {
Card card = new Card(rank, suit);
cards.add(card);
}
}
return cards;
}
И отсортировать их следующим образом:
Collections.sort(cards);
Эта версия sort разместит элементы в так называемом «естественном порядке», поскольку это определяется самим объектом.
Однако можно принудительно реализовать различный порядок сортировки, предоставив объект Comparator. Например, естественный порядок объектов Card будет рассматривать Ace как наименьшую карту, но в некоторых играх с игральными картами он имеет самый высокий ранг. Мы можем определить компаратор, рассматривающий Ace как самую высокую карту следующим образом:
Comparator<Card> comparator = new Comparator<Card>() {
@Override
public int compare(Card card1, Card card2) {
if (card1.getSuit() < card2.getSuit()) {
return -1;
}
if (card1.getSuit() > card2.getSuit()) {
return 1;
}
int rank1 = getRankAceHigh(card1);
int rank2 = getRankAceHigh(card2);
if (rank1 < rank2) {
return -1;
}
if (rank1 > rank2) {
return 1;
}
return 0;
}
private int getRankAceHigh(Card card) {
int rank = card.getRank();
if (rank == 1) {
return 14;
} else {
return rank;
}
}
};
Этот код определяет анонимный класс, который реализует compare по мере необходимости. Затем он создает экземпляр нового определенного анонимного класса. Если вы не знакомы с анонимными классами Java, вы можете прочитать о них на сайте thinkdast.com/anonclass.
Используя этот компаратор, мы можем вызвать sort следующим образом:
Collections.sort(cards, comparator);
В этом порядке черная вишня Ace является самой высокой картой в колоде; черви два — самые низкие.
Если вы хотите попробовать код этой части, он находится в Card.java. В качестве упражнения вы, возможно, захотите написать компаратор, сначала по рангу, а затем по масти, так что все тузы должны быть вместе, как и все двойки. И так далее. Для запросов с несколькими поисковыми словами общая релевантность каждой страницы в настоящее время является суммой релевантности каждого поискового слова. Подумайте, когда эта простая версия может не работать должным образом и попробуйте некоторые альтернативные решения.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )