自动补全已经变成了每一个应用程序基础部分特性。构建一个快速的,可扩展的自动补全对一个一直在增长数据的程序来说是个相当大的工程。我见过的其中最好的自动补全是Quora的搜索自动补全。我知道google和youtube也有最好的,但是我只想聚焦于小公司而不是巨头。这是一篇在Quora’s
autocomplete上解释的他们如何用C++设计构建的自动补全。
在这篇文章中,我们将使用solr搜索殷勤提供一个像样的自动补全。Solr像一个文档数据库一样工作,在这个数据库中,一条记录就是一个文档。在Solr中你用所有可能的域定义一个schema。当你定义域的时候同时也指定了每个域的类型。例如:
<field name="username" type="text" indexed="true" stored="true" multiValued="false" /> <field name="category" type="text" indexed="true" stored="true" multiValued="false" /> <field name="create_date" type="date" indexed="true" stored="true" multiValued="false" /> <field name="pins" type="long" indexed="true" stored="true" multiValued="false" />
正如你看到的,我们把username和category定义为text类型,create_date定义为date类型,pins定义为long类型。这使Solr可以基于域类型执行一些高级查询。对于自动补全,我们需要一个随着搜素可以提供子文本的文本域。但是除非你专门为它设计了一个解析器,否则Solr在匹配字符串上面做的并不是很好。有两种解析器是NGram和EdgeNGram。
NGramTokenizerFactory是一个可以把字符串切成指定长度的字符串块的解析器。这个分词器有两个参数:
<fieldType name="text" class="solr.TextField" > <analyzer type="index"> <tokenizer class="solr.KeywordTokenizerFactory"/> <filter class="solr.LowerCaseFilterFactory"/> <filter class="solr.NGramFilterFactory" minGramSize="1" maxGramSize="15" /> </analyzer> <analyzer type="query"> <tokenizer class="solr.KeywordTokenizerFactory"/> <filter class="solr.LowerCaseFilterFactory"/> </analyzer> </fieldType>
在你的Solr中使用这个设置将会是Solr把文本截成子字符串执行匹配。这可能会产生不良的结果,并且使solr做不需要的子字符串计算。例如:“Steve”被切分为"st","te","ev","ve","ste","tev"等等。
EdgeNGramFilterFactory是一个子字符串来自于边界的解析器。也就是要么来自于单词前面要么来自于单词后面。例如:"Steve"将被解析为"ste","eve","stev"等等。
<fieldType name="prefix_token" class="solr.TextField" positionIncrementGap="1"> <analyzer type="index"> <tokenizer class="solr.WhitespaceTokenizerFactory" /> <filter class="solr.LowerCaseFilterFactory" /> <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/> <filter class="solr.EdgeNGramFilterFactory" minGramSize="2" maxGramSize="15" side="front" /> </analyzer> <analyzer type="query"> <tokenizer class="solr.WhitespaceTokenizerFactory" /> <filter class="solr.LowerCaseFilterFactory" /> <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/> </analyzer> </fieldType>
上面的设置定义边界块来生成2到15之间大小的块并且只考虑了做前边界的块。这个效果对自动补全目的是相当不错的。
由于我们定义了一个新域类型"prefix_token",我们就可以在solr中设置prefix_token的类型的域。
<field name="fullname" type="prefix_token" indexed="true" stored="true" multiValued="false" />
现在用全名查询Solr将产生一个好的结果。假设你只需要users并且忽略其它的文档,那么你可以添加另一个条件选择"user"类型的文档。你也可以使用帮助用高社交媒体得分来推送结果到顶部。
?q={!boost b=log(social_score)}(fullname:Steve)
注意:Solr支持TermsComponent,它给一个查询字符串返回一个拼写检查的数组。但是我们需要一个自动补全,从那上面选择一个结果重定向到特别的文档url。还有我们想要在自动补全中展示用户的头像,那就意味着我们需要完成文档而不是拼写支持。