【问题标题】:How to implement autocomplete on a massive dataset如何在海量数据集上实现自动完成
【发布时间】:2010-10-15 07:20:42
【问题描述】:

我正在尝试在我正在构建的网站上实现类似 Google 建议的功能,并且很好奇如何处理非常大的数据集。当然,如果您有 1000 个项目,您可以缓存这些项目并循环它们。但是,当您拥有一百万件物品时,您将如何处理呢?此外,假设这些项目不是一个词。具体来说,我对 Pandora.com 印象深刻。例如,如果您搜索“wet”,它会返回“Wet Sand”,但也会返回 Toad The Wet Sprocket。他们的自动完成功能很快。我的第一个想法是按前两个字母对项目进行分组,所以你会有类似的东西:

Dictionary<string,List<string>>

其中的关键是前两个字母。没关系,但是如果我想做类似于 Pandora 的事情并允许用户看到匹配字符串中间的结果怎么办?根据我的想法:Wet 永远不会与 Toad the Wet Sprocket 匹配,因为它将位于“TO”存储桶而不是“WE”存储桶中。那么也许您可以将字符串拆分,并将“Toad the Wet Sprocket”放入“TO”、“WE”和“SP”存储桶(去掉“THE”这个词),但是当您正在谈论一百万个条目,每个条目可能不得不说几句话,这似乎您很快就会开始消耗大量内存。好的,这是一个很长的问题。想法?

【问题讨论】:

  • 你能提供一些关于你的数据的数字吗?几串?平均长度和平均字数?哪种语言?
  • 目前有 496,000 个字符串。平均长度 14 个字符。平均字数 2.3。 C#。哦,还有像Л和И这样的非ASCII字符
  • 我已经实现了一个自动完成解决方案,使用 docker compose(以便更容易理解),源代码公开可用:lopespm.github.io/2020/08/03/…。希望有帮助

标签: optimization autocomplete


【解决方案1】:

正如我在How to implement incremental search on a list 中指出的那样,您应该使用TriePatricia trie 之类的结构来搜索大文本中的模式。

对于发现某些文本中间的模式,有一个简单的解决方案。我不确定这是否是最有效的解决方案,但我通常会这样做。

当我在 Trie 中插入一些新文本时,我只是插入它,然后删除第一个字符,再次插入,删除第二个字符,再次插入......等等,直到整个文本被消耗完。然后,您只需从根开始一次搜索即可发现每个插入文本的每个子字符串。生成的结构称为Suffix Tree,有很多可用的优化。

而且速度非常快。要查找包含给定 n 个字符序列的所有文本,您必须检查最多 n 个节点并在每个节点的子列表中执行搜索。根据子节点集合的实现(数组、列表、二叉树、跳过列表),假设仅不区分大小写的拉丁字母,您可能只需 5 个搜索步骤即可识别所需的子节点。插值排序可能有助于大型字母和具有许多子节点的节点,这些节点通常位于根附近。

【讨论】:

  • Trie 非常适合在字符串的开头查找匹配项。但是,对于我当前的数据集,删除第一个字符然后插入的过程并没有很好地工作,只是开始使用太多内存:> 1 gig 在数据集完成一半之前。
  • 可能是过早优化的情况,当我刚刚运行一个天真的“包含”搜索时,运行时间不到 100 毫秒。 Lucene 看起来也很酷,所以我可以试试看。另一个想法是结合使用 trie 和朴素搜索。
  • 从 trie 开始,如果你有少于 20 个匹配的开始,回到天真。为什么我只能插入 300 个字符??!
  • 标准 Trie 非常占用内存,对于较大的集合,您想使用 Compacted Trie,从而大大减少内存占用。其他优化包括节点值的延迟初始化和子/值集的正确数据结构。不久前,我创建了一个 Java autocomplete library 能够处理非常大的数据集(10,000,000+)并有效地回答精确和近似搜索。
  • 我喜欢@Daniel 的回答。
【解决方案2】:

不要尝试自己实现这个(除非你只是好奇)。使用 Lucene 或 Endeca 之类的东西 - 它会节省您的时间和头发。

【讨论】:

  • Lucene 看起来真的很酷,感谢您的建议!但是,是的,我当然很好奇! :)
【解决方案3】:

在算法上与您的要求无关,但请确保在 kaypress(es) 之后有 200 毫秒或更长时间的延迟(滞后),以便确保用户在发出异步请求之前已停止输入。这样可以减少对服务器的冗余 http 请求。

【讨论】:

    【解决方案4】:

    我会使用类似于trie 的东西,并让每个叶节点的值成为包含叶节点表示的单词的可能性列表。您可以按可能性顺序对它们进行排序,或者根据用户在搜索框中输入的其他词对它们进行动态排序/过滤,等等。它将在合理的 RAM 中快速执行。

    【讨论】:

      【解决方案5】:

      您将项目保存在服务器端(可能在数据库中,如果数据集非常庞大且复杂),然后从客户端浏览器发送 AJAX 调用,该调用使用 json/xml 返回结果。您可以响应用户输入或使用计时器来执行此操作。

      【讨论】:

        【解决方案6】:

        如果你不想要一个 trie 并且你想要字符串中间的东西,你通常想要运行某种编辑距离函数(levenshtein distance),它会给你一个数字,表明 2 个字符串的匹配程度.这不是一个特别有效的算法,但对于像单词这样的东西来说并不重要,因为它们相对较短。如果您要对 8000 个字符串进行比较,则可能需要几秒钟。我知道大多数语言都有实现,或者你可以在互联网上很容易地找到它的代码/伪代码。

        【讨论】:

          【解决方案7】:

          我已经为这个场景构建了AutoCompleteAPI

          注册以获取私有索引,然后, 上传您的文件。

          在文档“New York”上使用 curl 上传示例:

          curl -X PUT -H "Content-Type: application/json" -H "Authorization: [YourSecretKey]" -d '{
          "key": "New York",
          "input": "New York"
          }' "http://suggest.autocompleteapi.com/[YourAccountKey]/[FieldName]"
          

          索引所有文档后,要获得自动完成建议,请使用:

          http://suggest.autocompleteapi.com/[YourAccountKey]/[FieldName]?prefix=new
          

          您可以使用任何客户端自动完成库向用户显示这些结果。

          【讨论】:

            猜你喜欢
            • 2018-02-04
            • 1970-01-01
            • 2012-01-03
            • 1970-01-01
            • 1970-01-01
            • 2016-06-23
            • 2018-08-08
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多