模糊搜索算法(近似字符串匹配算法)

Posted

技术标签:

【中文标题】模糊搜索算法(近似字符串匹配算法)【英文标题】:Fuzzy search algorithm (approximate string matching algorithm) 【发布时间】:2015-11-27 00:23:16 【问题描述】:

我希望创建一个模糊搜索算法。 然而,经过数小时的研究,我真的很挣扎。

我想创建一个算法,对学校名称列表执行模糊搜索。

这是我目前看到的:

我的大部分研究都指向 Google 和 *** 上的“字符串指标”,例如:

Levenshtein 距离 Damerau-Levenshtein 距离 Needleman-Wunsch 算法

但是,这只是给出了 相似 2 个字符串的分数。我能想到将其实现为搜索算法的唯一方法是执行线性搜索并为每个字符串执行字符串度量算法并返回分数高于某个阈值的字符串。 (最初我将字符串存储在 trie 树中,但这显然对我没有帮助!)

虽然这对于小型列表来说不是一个坏主意,但对于具有 100,000 个名称并且用户执行了许多查询的列表来说,这将是一个问题。

我研究的另一种算法是拼写检查方法,您只需搜索所有可能的拼写错误。然而,这也是非常低效的,因为它需要超过 75,000 个字,长度为 7 的字和错误计数仅为 2。

我需要什么?

谁能给我推荐一个高效的模糊搜索算法。与:

算法名称 工作原理或工作原理链接 优缺点以及最佳使用时间(可选)

我了解所有算法都有其优缺点,没有最佳算法。

【问题讨论】:

看看这个,看看有没有帮助:***.com/questions/491148/… 好的实现很复杂,所以您可能需要考虑像 Lucene 这样的现成实现。 可能使用搜索方法创建三叉树,该搜索方法在运行中从树中计算编辑距离。这并不容易,但我以前做过一个,它可以工作。 查看答案***.com/a/58791875/140837 【参考方案1】:

考虑到您正在尝试对学校名称列表进行模糊搜索,我认为您不想使用传统的字符串相似性,例如 Levenshtein 距离。我的假设是您正在接受用户的输入(键盘输入或通过电话说话),并且您希望快速找到匹配的学校。

距离指标会根据替换、删除和插入告诉您两个字符串的相似程度。但是这些算法并没有真正告诉您字符串在人类语言中的相似程度作为单词

以“smith”、“smythe”和“smote”为例。我可以分两步从“smythe”到“smith”:

smythe -> smithe -> smith

从“smote”到“smith”分两步:

smote -> smite -> smith

所以两者的距离和strings一样,但是作为words,它们却有很大的不同。如果有人告诉你(口语)他正在寻找“Symthe College”,你几乎肯定会说,“哦,我想你是说 Smith。”但如果有人说“斯莫特学院”,你就不会知道他在说什么。

您需要的是phonetic algorithm,例如Soundex 或Metaphone。基本上,这些算法将一个单词分解为音素,并创建一个表示该单词在口语中的发音方式。然后,您可以将结果与已知的单词列表进行比较以找到匹配项。

这样的系统会比使用距离度量快得多。考虑使用距离度量,您需要将用户的输入与列表中的每个单词进行比较以获得距离。这在计算上是昂贵的,而且正如我用“smith”和“smote”演示的那样,结果可能很糟糕。

使用语音算法,您可以创建每个已知单词的音素表示并将其放入字典(哈希映射或可能的 trie)中。这是一次性的启动成本。然后,每当用户输入搜索词时,您就创建他输入的音素表示并在您的字典中查找它。这样会快很多并且产生更好的结果。

还要考虑到,当人们拼错专有名称时,他们几乎总是能正确地拼出第一个字母,而且往往发音错误的 听起来像他们试图拼写的实际单词。如果是这样,那么语音算法肯定是要走的路。

【讨论】:

这看起来不错。好建议!我遇到的一个问题是,没有尝试通过按相邻键来检查意外错误输入,例如单击“s”而不是“a”。有没有中道。 [Metaphone 3 的商业化也有点推迟]。但还是不错的建议 @YahyaUddin:我不会太担心 Metaphone 3 的商业化。 Soundex 运行良好。我没有用过 Metaphone,所以我不能说它的效果如何。 @YahyaUddin:期望算法具有足够的弹性以注意到输入错误的字母('l' 表示 's',或 's' 表示 'a' 等)更加困难。我怀疑如果算法没有返回结果,或者返回完全不合适的结果,用户会注意到他的错误并纠正它。但是,您可以采用混合方法。创建所有名称的 trie,如果语音算法没有返回或返回很少,则搜索它。研究拼字游戏和其他文字游戏如何填补缺失的字母或告诉您可以从一袋字母中组成哪些单词等。【参考方案2】:

我写了一篇关于我如何实现模糊搜索的文章:

https://medium.com/@Srekel/implementing-a-fuzzy-search-algorithm-for-the-debuginator-cacc349e6c55

实现在 Github 中并且在公共领域,所以请随意看看。

https://github.com/Srekel/the-debuginator/blob/master/the_debuginator.h#L1856

它的基本原理是:将您要搜索的所有字符串分成几部分。所以如果你有路径,那么“C:\documents\lol.txt”可能是“C”、“documents”、“lol”、“txt”。

确保将这些字符串小写以确保不区分大小写。 (也许只有在搜索字符串全小写时才这样做)。

然后将您的搜索字符串与此匹配。就我而言,无论顺序如何,我都想匹配它,所以即使“lol”出现在“doc”之后,“loldoc”仍然会匹配上述路径。

匹配需要有一些得分才能很好。我认为最重要的部分是连续匹配,所以一个接一个地匹配的字符越多越好。所以“doc”比“dcm”好。

那么您可能希望在部分开始的比赛中获得额外分数。因此,“doc”比“ocu”获得的分数更高。

在我的例子中,我还为匹配部件的 end 给出了更多的分数。

最后,您可能需要考虑为匹配最后部分加分。这使得匹配文件名/结尾的分数高于指向它的文件夹。

【讨论】:

【参考方案3】:

您将模糊搜索算法与实现混淆了:一个单词的模糊搜索可能会返回所有具有 Levenshtein 距离为 2 的单词的 400 个结果。但是,对于用户,您必须只显示前 5 个-10.

在实现方面,您将预处理字典中的所有单词并将结果保存到数据库中。流行词(以及它们的模糊词)将被保存到缓存层中 - 因此您不必为每个请求都访问数据库。

您可以添加一个 AI 层,该层将添加最常见的拼写错误并将它们添加到数据库中。等等。

【讨论】:

当你说“预处理”我的话时,没有查询我怎么能​​做到这一点。 Levenshtein 只能根据我认为必须由用户给出的查询字符串来计算 @YahyaUddin 不要脱离上下文 :) 我写道:“预处理字典中的所有单词”(这方面的“字典”可以是所有学校和 Levenshtein 距离为 1-2 的所有字符串选项)这是一个漫长的过程,您将离线运行,当您完成时 - 您将存储字典中单词之间的所有连接在你的数据库中。 所以这基本上是“拼写检查方法”。不幸的是,正如问题中提到的,这种方法在内存方面效率很低(尽管在计算方面很有效),因为它需要超过 75,000 个条目来处理长度为 7 的单词。现在,如果我有 100,000 个条目,那将是一个数量惊人的记录。更不用说大多数学校名称都远远超过 7 个字符。 @YahyaUddin 它在内存方面并不是非常低效,它在存储方面非常低效。您应该根据最常见的情况决定要缓存的内容。诚然,总会有取舍。也许您一开始并不打算使用模糊算法,如果是这样的话,Jim 的建议可能更适合您! 对不起,我的意思是存储【参考方案4】:

“一种模糊搜索”的简单算法

说实话,在某些情况下,模糊搜索几乎没有用,我认为更简单的算法可以改善搜索结果,同时提供我们仍在执行模糊搜索的感觉。

这是我的用例:使用“模糊搜索”过滤国家/地区列表

我正在使用的列表有两个以 Z 开头的国家:赞比亚和津巴布韦。

我使用的是Fusejs。

在这种情况下,当输入针“zam”时,结果集有 19 个匹配项,并且与列表底部的任何人类(赞比亚)最相关的匹配项。结果中的大多数其他国家的名字甚至都没有字母 z。

这是一个移动应用程序,您可以在其中从列表中选择一个国家/地区。这应该很像您必须从手机的联系人中选择联系人时。您可以通过在搜索框中输入一些词来过滤联系人列表。

恕我直言,这种有限的搜索内容不应该被人们问“这到底是什么?!?”。

有人可能会建议按最相关的匹配进行排序。但在这种情况下这是不可能的,因为用户将始终必须在缩减列表中直观地找到“感兴趣的项目”。请记住,这应该是一个过滤工具,而不是“à la Google”搜索引擎。所以结果应该以可预测的方式排序。在过滤之前,排序是按字母顺序排列的。所以过滤后的列表应该只是原始列表的按字母顺序排序的子集。

所以我想出了以下算法...

    抓住针……在这种情况下:zam 在针的开头和结尾插入.* 图案 在针的每个字母之间插入.* 图案 使用现在为.*z.*a.*m.* 的新指针在大海捞针中执行正则表达式搜索

在这种情况下,用户将通过查找以某种方式以某种方式出现字母 z、a 和 m 的所有内容来获得预期的结果。针中的所有字母将以相同的顺序出现在比赛中。

这也将匹配国家名称,如 Mozambique ... 这是完美的。

我只是觉得有时候,我们不应该试图用火箭筒杀死一只苍蝇。

【讨论】:

出于某种原因,这真的让我思考。我喜欢你算法的简单和优雅。为了进一步改进它以解决拼写错误,是否也可以查看某种附近的键映射以替代针中的字符?或者这个正则表达式是否已经解决了 zsm (zam) 错字之类的问题? 谢谢。好主意啊。眼下。这个正则表达式没有使用任何错字检测技术,添加它肯定是一件好事。【参考方案5】:

谁能给我推荐一个高效的模糊搜索算法。与:

在这个存储库中,我收集了一个简单(但快速)的算法来执行模糊搜索,就像在 Sublime Text、VSCode 等编辑器中完成的那样(即,只需几次击键,您就可以得到过滤的条目结果以模糊的方式匹配键入的字符):

https://github.com/tajmone/fuzzy-search

该算法是由 Forrest Smith 编写的,简称为“fts_fuzzy_match”。在存储库中,您会发现以 10 多种不同语言实现的同一算法的两种变体。

原始文章可以在这里找到:

Reverse Engineering Sublime Text’s Fuzzy Match

【讨论】:

【参考方案6】:

Fuzzy Sort 是一个 javascript 库,有助于从大量数据中执行字符串匹配。

以下代码将有助于在 react.js 中使用模糊排序。

通过npm安装模糊排序,

npm install fuzzysort

react.js 中的完整演示代码

import React from 'react';
import './App.css';
import data from './testdata';
const fuzzysort = require('fuzzysort');

class App extends React.Component 
  constructor(props)
    super(props)
    this.state = 
      keyword: '',
      results: [],
    
    console.log("data: ", data["steam_games"]);
  

  search(keyword, category)   
    return fuzzysort.go(keyword, data[category]);
  

  render()
    return (
      <div className="App">
        <input type="text" onChange=(e)=> this.setState(keyword: e.target.value)
          value=this.state.keyword
        />
        <button onClick=()=>this.setState(results: this.search(this.state.keyword, "steam_games"))>Search</button>
        this.state.results !== null && this.state.results.length > 0 ?
          <h3>Results:</h3> : null
        
        <ul>
        this.state.results.map((item, index) =>
            return(
              <li key=index>item.score : item.target</li>
            )
          )
        
        </ul>
      </div>
    );
  


export default App;

更多信息请参考FuzzySort

【讨论】:

【参考方案7】:

问题可以分为两部分:

1) 选择正确的字符串指标。

2) 提出相同的快速实施方案。

选择正确的指标:这部分很大程度上取决于您的用例。但是,我建议结合使用基于距离的分数和基于语音的编码以获得更高的准确性,即最初根据 Levenshtein 距离计算分数,然后使用 Metaphone 或 Double Metaphone 来补充结果。

同样,您应该根据您的用例做出决定。如果您可以只使用 Metaphone 或 Double Metaphone 算法,那么您不必担心计算成本。

实施:限制计算成本的一种方法是根据您的用例将您的数据分成几个小组,然后将它们加载到字典中。

例如,如果您可以假设您的用户正确输入了姓名的第一个字母,您可以将基于此不变量的姓名存储在字典中。

因此,如果用户输入名称“National School”,您只需计算以字母“N”开头的学校名称的模糊匹配分数

【讨论】:

以上是关于模糊搜索算法(近似字符串匹配算法)的主要内容,如果未能解决你的问题,请参考以下文章

“模糊匹配”字符串的算法

求一个基于java的模糊匹配算法

使用后缀树进行近似子串匹配

python模糊匹配库能否定制匹配关系

nodejs 匹配字符串问题

【算法笔记】字符串匹配