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

时间:2015-09-01 16:58:14

标签: string algorithm search levenshtein-distance fuzzy-search

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

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

这是我到目前为止所看到的:

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

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

然而,这只是给出了类似 2个字符串的得分。我认为将其实现为搜索算法的唯一方法是执行线性搜索并对每个字符串执行字符串度量算法,并返回分数高于特定阈值的字符串。 (最初我把我的琴弦存放在一棵树上,但这显然不会帮助我!)

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

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

我需要什么?

有人可以建议我良好有效的模糊搜索算法。用:

  • 算法名称
  • 工作原理或工作原理链接
  • Pro和cons以及最佳使用时间(可选)

据我所知,所有算法都有其优缺点,并且没有最佳算法。

7 个答案:

答案 0 :(得分:32)

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

距离指标告诉您两个字符串基于替换,删除和插入的相似程度。但是这些算法并没有真正告诉你任何关于字符串与人类语言中的单词有多相似的内容。

例如,考虑“史密斯”,“smythe”和“smote”这两个词。我可以分两步走出“smythe”到“smith”:

smythe -> smithe -> smith

从“击打”到“史密斯”两步:

smote -> smite -> smith

所以这两个与字符串的距离相同,但是作为,它们有很大的不同。如果有人告诉你(口语)他正在寻找“Symthe学院”,你几乎肯定会说,“哦,我认为你的意思是史密斯。”但如果有人说“斯莫特学院”,你就不会知道他在说什么。

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

这样的系统比使用距离度量更快 。考虑到使用距离度量,您需要将用户的输入与列表中的每个单词进行比较以获得距离。这在计算上是昂贵的,结果,正如我用“史密斯”和“击打”所证明的那样,可笑得很糟糕。

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

另外考虑一下,当人们拼错正确的名字时,他们几乎总是得到正确的第一个字母,并且经常发出错误拼写的听起来像他们试图拼写的实际单词。如果是这种情况,那么语音算法肯定是要走的路。

答案 1 :(得分:5)

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

实施方面,您将预处理字典中的所有单词并将结果保存到数据库中。流行的单词(及其模糊的单词)将被保存到缓存层中 - 因此您不必为每个请求点击数据库。

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

答案 2 :(得分:3)

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

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”。

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

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

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

然后你可能想要为一个部分的开始的比赛提供额外的分数。所以你获得的“doc”比“ocu”更多。

在我的情况下,我还提供了更多积分来匹配零件的结束

最后,您可能需要考虑为匹配最后部分提供额外分数。这使得匹配文件名/结尾分数高于导致它的文件夹。

答案 3 :(得分:3)

有人可以建议我一个好的高效的模糊搜索算法。与:

在此存储库中,我收集了一个简单(但快速)的算法,以执行类似于Sublime Text,VSCode等编辑器中的操作的模糊搜索方式。(例如,只需几次按键操作,您就可以过滤出以模糊方式匹配输入的字符):

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

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

答案 4 :(得分:2)

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

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

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

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

我正在使用Fusejs

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

这是用于移动应用程序的,您可以从列表中选择一个国家。它本来就像是您必须从电话的联系人中选择一个联系人时一样。您可以通过在搜索框中输入一些字词来过滤联系人列表。

恕我直言,这种有限的搜索内容不应以人们会问“到底是什么?!”的方式对待。

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

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

  1. 抓针...在这种情况下:扎姆
  2. 在针的开始和末端插入.*模式
  3. 在针的每个字母之间插入.*模式
  4. 使用现在是.*z.*a.*m.*的新指针在干草堆中执行正则表达式搜索

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

这还将匹配像Mo zam bique这样的国家/地区名称,这很完美。

我只是认为有时候,我们不应该尝试用火箭筒杀死苍蝇。

答案 5 :(得分:0)

模糊排序是一个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

答案 6 :(得分:0)

问题可以分为两部分:

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

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

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

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

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

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

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