diff --git a/16.md b/16.md index 26f078efebcd0bfb72a1fd4117460bf5766632b8..9b8ba745af78fff14c6281f013abedf755bdc13c 100644 --- a/16.md +++ b/16.md @@ -1,5 +1,13 @@ # 第十六章 布尔搜索 +> 原文:[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/) + +> 自豪地采用[谷歌翻译](https://translate.google.cn/) + 在本章中,我展示了上一个练习的解决方案。然后,你将编写代码来组合多个搜索结果,并按照它与检索词的相关性进行排序。 ## 16.1 爬虫的答案 @@ -125,8 +133,186 @@ public class WikiCrawler { 在这种情况下: -+ `s1`和`s2`的交集是含有的“java”和“编程”的页面集。 -+ `s1`和`s2`的并集是含有的“java”或“编程”的页面集。 ++ `s1`和`s2`的交集是含有“java”和“编程”的页面集。 ++ `s1`和`s2`的并集是含有“java”或“编程”的页面集。 + `s1`与`s2`的差集是含有“java”而不含有“印度尼西亚”的页面集。 在下一节中,你将编写实现这些操作的方法。 + +## 16.4 练习 13 + +在本书的仓库中,你将找到此练习的源文件: ++ ++ `WikiSearch.java`,它定义了一个对象,包含搜索结果并对其执行操作。 ++ `WikiSearchTest.java`,它包含`WikiSearch`的测试代码。 ++ `Card.java`,它演示了如何使用`java.util.Collections`的`sort`方法。 + +你还将找到我们以前练习中使用过的一些辅助类。 + +这是`WikiSearch`类定义的起始: + +```java +public class WikiSearch { + + // map from URLs that contain the term(s) to relevance score + private Map map; + + public WikiSearch(Map map) { + this.map = map; + } + + public Integer getRelevance(String url) { + Integer relevance = map.get(url); + return relevance==null ? 0: relevance; + } +} +``` + +`WikiSearch`对象包含 URL 到它们的相关性分数的映射。在信息检索的上下文中,“相关性分数”用于表示页面多么满足从查询推断出的用户需求。相关性分数的构建有很多种方法,但大部分都基于“检索词频率”,它是搜索词在页面上的显示次数。一种常见的相关性分数称为 TF-IDF,代表“检索词频率 - 逆向文档频率”。你可以在 上阅读更多信息 。 + +你可以选择稍后实现 TF-IDF,但是我们将从一些更简单的 TF 开始: + ++ 如果查询包含单个检索词,页面的相关性就是其词频;也就是说该词在页面上出现的次数。 ++ 对于具有多个检索词的查询,页面的相关性是检索词频率的总和;也就是说,任何检索词出现的总次数。 + +现在你准备开始练习了。运行`ant build`来编译源文件,然后运行 `ant WikiSearchTest`。像往常一样,它应该失败,因为你有工作要做。 + +在`WikiSearch.java`中,填充的`and`,`or`以及`minus`的主体,使相关测试通过。你不必担心`testSort`。 + + +你可以运行`WikiSearchTest`而不使用`Jedis`,因为它不依赖于 Redis 数据库中的索引。但是,如果要对索引运行查询,则必须向文件提供有关`Redis`服务器的信息。详见 14.3 节。 + + +运行`ant JedisMaker`来确保它配置为连接到你的 Redis 服务器。然后运行`WikiSearch`,它打印来自三个查询的结果: + ++ “java” ++ “programming” ++ “java AND programming” + +最初的结果不按照特定的顺序,因为`WikiSearch.sort`是不完整的。 + +填充`sort`的主体,使结果以递增的相关顺序返回。我建议你使用`java.util.Collections`提供的`sort`方法,它可以排序任何种类的`List`。你可以阅读 上的文档 。 + +有两个`sort`版本: + ++ 单参数版本接受列表并使用它的`compareTo`方法对元素进行排序,因此元素必须是`Comparable`。 ++ 双参数版本接受任何对象类型的列表和一个`Comparator`,它是一个提供`compare`方法的对象,用于比较元素。 + +如果你不熟悉`Comparable`和`Comparator`接口,我将在下一节中解释它们。 + +## 16.5 `Comparable`和`Comparator` + +本书的仓库包含了`Card.java`,它演示了两个方式来排序`Card`对象的列表。这里是类定义的起始: + +```java +public class Card implements Comparable { + + 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`: + +```java + 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`小于`that`,则应该返回一个负数,如果它更大,则为正数,如果它们相等则为`0`。 + +如果使用单参数版本的`Collections.sort`,它将使用元素提供的`compareTo`方法对它们进行排序。为了演示,我们可以列出`52`张卡,如下所示: + +```java + public static List makeDeck() { + List cards = new ArrayList(); + 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; + } +``` + +并这样排序它们: + +```java + Collections.sort(cards); +``` + +这个版本的`sort`将元素按照所谓的“自然秩序”放置,因为它由对象本身决定。 + +但是可以通过提供一个`Comparator`对象,来强制实现不同的排序。例如,`Card`对象的自然顺序将`Ace`视为最小的牌,但在某些纸牌游戏中,它的排名最高。我们可以定义一个`Comparator`,将`Ace`视为最大的牌,像这样: + +```java + Comparator comparator = new Comparator() { + @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 中的匿名类,可以在 上阅读它们。 + +使用这个`Comparator`,我们可以这样调用`sort`: + +```java + Collections.sort(cards, comparator); +``` + + +在这个顺序中,黑桃的`Ace`是牌组上的最大的牌;梅花二是最小的。 + +如果你想试验这个部分的代码,它们在`Card.java`中。作为一个练习,你可能打算写一个比较器,先按照`rank`,然后再按照`suit`,所以所有的`Ace`都应该在一起,所有的二也是。以此类推。 + +## 16.6 扩展 + +如果你完成了此练习的基本版本,你可能需要处理这些可选练习: + ++ 请阅读 上的 TF-IDF,并实现它。你可能需要修改`JavaIndex`来计算文档频率;也就是说,每个检索词在索引的所有页面上出现的总次数。 ++ 对于具有多个检索词的查询,每个页面的总体相关性目前是每个检索词的相关性的总和。想想这个简单版本什么时候可能无法正常运行,并尝试一些替代方案。 ++ 构建用户界面,允许用户输入带有布尔运算符的查询。解析查询,生成结果,然后按相关性排序,并显示评分最高的 URL。考虑生成“片段”,它显示了检索词出现在页面的哪里。如果要为用户界面制作 Web 应用程序,请考虑将 Heroku 作为简单选项,用于 开发和部署 Java Web应用程序。见