【问题标题】:Algorithm for fast substring search in a contact database联系人数据库中快速子串搜索的算法
【发布时间】:2012-05-28 15:11:18
【问题描述】:

我有一个包含大约 100 万个姓名和地址的数据库。该数据库应公开用于在网页上进行类似“Google Suggest”的即时搜索。我正在寻找一种有效的算法/数据结构来帮助我实现这一目标。

使这比仅使用TrieGeneralized Suffix Tree 更困难的是,它必须支持省略某些名称的查询。例如,当用户键入“Elvis Pr”时,应建议“Elvis Aaron Presley”。

我希望在内存中获取整个索引(我有大约 4GB 的 RAM 可用于此)。

该应用程序是用 Java 编写的,因此指向基于 Java 的库的链接被认为是非常有用的。我一直在看LuceneMG4J,但我还没有弄清楚我可以使用哪种类型的索引来解决我的问题。

【问题讨论】:

    标签: java algorithm lucene full-text-search search-engine


    【解决方案1】:

    也许您真正想要的是搜索,其中用户输入的每个单词都必须显示为联系人中某个单词的前缀。这比一般的子字符串搜索更容易和更快。

    1. 构建属于任何联系人的所有单词的单个排序数组,并在每个单词旁边存储一个“联系人 ID”字段(例如 [Aaron/1, Aleksander/2, Blomskøld/2, Elvis/1, Presley/1] )。
    2. 对于用户键入的每个单词,分别使用二分搜索来查找以该单词开头的名称范围(这必须是数组中连续的索引范围)。由于用户每次击键通常只调整一个单词,因此您只需在每次击键时重新计算其中一个范围 - 事实上,即使在输入额外字母的常见情况下,甚至这个重新计算步骤也可以更有效地完成,因为这只能缩小匹配词的范围。
    3. 最后,将联系人 ID 集合相交以生成可能性列表。为了显示可能性,您需要第二个数组,按联系人 ID 索引并包含全名。

    【讨论】:

    • 如果添加了新联系人,删除了联系人怎么办?我需要求助于数组吗?!
    • @Doggynub:你不需要求助——你可以在 O(n) 时间内删除一个元素,或者找到合适的位置插入一个元素,然后在 O(n )。诚然,这仍然很慢,因此最好维护一个单独的添加和删除排序列表,并在它变得足够大时将其与主列表合并。
    【解决方案2】:

    看看Cleo,看起来你有一个非常相似的用例。他们使用 Bloom 过滤器来搜索前缀,并使用 Forward 索引来去除误报。

    【讨论】:

    • Cleo 正是我想要的。谢谢!
    【解决方案3】:

    您可以尝试使用带有字符串距离度量的bk-tree,例如levenshtein,另请参阅http://blog.notdot.net/2007/4/Damn-Cool-Algorithms-Part-1-BK-Trees

    编辑: 发明距离度量很困难,但我碰巧知道一个可以使用的称为structural entropic distance 的度量,它基于信息差异。它的工作原理如下:

    取两个字符串 x = "Elvis Pr" 和 y = "Elvis Aaron Presley"

    为每个计算出一元和二元的多重集:

    x = {e, l, v, i, s, _, p, r, el, lv, vi, is, s_,  _p, pr}
    y = {ex3, lx2, v, i, sx2, _x2, ax2, rx2, o, n, p, y, el, lv, vi, is, s_, _a, aa, ar, ro, on, n_, _p, pr, re, es, sl, le, ey}
    

    现在对于两者中的那些术语

    {e, l, v, i, s, _, p, r, el, lv, vi, is, s_, _p, pr}
    

    计算产品(f_x(t) / (f_x(t) + f_y(t)))^{f_x(t)/2} * (f_y(t) / (f_x(t) + f_y(t)))^{f_y(t)/2} 所以

    e  = ((1/15) / (1/15 + 3/37))^(1/30) * ((3/37) / (1/15 + 3/37))^(3/74)
    l  = ((1/15) / (1/15 + 2/37))^(1/30) * ((2/37) / (1/15 + 2/37))^(2/74)
    v  = ((1/15) / (1/15 + 1/37))^(1/30) * ((1/37) / (1/15 + 1/37))^(1/74)
    i  = ((1/15) / (1/15 + 1/37))^(1/30) * ((1/37) / (1/15 + 1/37))^(1/74)
    s  = ((1/15) / (1/15 + 2/37))^(1/30) * ((2/37) / (1/15 + 2/37))^(2/74)
    _  = ((1/15) / (1/15 + 2/37))^(1/30) * ((2/37) / (1/15 + 2/37))^(2/74)
    p  = ((1/15) / (1/15 + 1/37))^(1/30) * ((1/37) / (1/15 + 1/37))^(1/74)
    r  = ((1/15) / (1/15 + 2/37))^(1/30) * ((2/37) / (1/15 + 2/37))^(2/74)
    el = ((1/15) / (1/15 + 1/37))^(1/30) * ((1/37) / (1/15 + 1/37))^(1/74)
    lv = ((1/15) / (1/15 + 1/37))^(1/30) * ((1/37) / (1/15 + 1/37))^(1/74)
    vi = ((1/15) / (1/15 + 1/37))^(1/30) * ((1/37) / (1/15 + 1/37))^(1/74)
    is = ((1/15) / (1/15 + 1/37))^(1/30) * ((1/37) / (1/15 + 1/37))^(1/74)
    s_ = ((1/15) / (1/15 + 1/37))^(1/30) * ((1/37) / (1/15 + 1/37))^(1/74)
    _p = ((1/15) / (1/15 + 1/37))^(1/30) * ((1/37) / (1/15 + 1/37))^(1/74)
    pr = ((1/15) / (1/15 + 1/37))^(1/30) * ((1/37) / (1/15 + 1/37))^(1/74)
    

    将所有这些相乘,您应该会得到一个范围为 [0.5, 1] 的数字,因此您可以通过乘以 2 并减去 1 更有效地将其缩放到范围 [0,1]。

    但是,这不是离散距离度量,因此您必须使用另一个度量索引,例如 vp-tree

    【讨论】:

    • 谢谢,我去看看 bk-trees。我想我需要制定一个与普通 levenshtein 不同的距离度量,例如在搜索中缺少一个(可能很长的)中间名应该返回一个低距离。
    • 我知道另一个指标,我已经编辑了我的答案来描述它
    【解决方案4】:

    Solr 提供全功能自动建议功能 OOTB。 This link 提供了一些从流行查询生成的背景。但是您可以轻松地对其进行调整以构建一些先验知识,例如您描述的场景。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-05-07
      • 1970-01-01
      • 1970-01-01
      • 2021-01-24
      • 2012-06-03
      • 2010-12-18
      • 2019-01-28
      • 2011-08-31
      相关资源
      最近更新 更多