我正在寻找一个模糊搜索JavaScript库来过滤数组。我已尝试使用fuzzyset.js和fuse.js,但结果非常糟糕(您可以尝试在链接网页上进行演示)。
在对Levenshtein距离进行一些阅读之后,它对我来说是一个很糟糕的近似用户在打字时所寻找的东西。对于那些不知道的人,系统会计算需要多少次插入,删除和替换才能使两个字符串匹配。
在Levenshtein-Demerau模型中修复的一个明显缺陷是 blub 和 boob 被认为与 bulb 同样相似(每个需要两次替换)。但很明显,灯泡与 blub 更相似,而不是 boob ,我刚才提到的模型通过允许换位
我想在文本完成的上下文中使用它,所以如果我有一个数组['international', 'splint', 'tinder']
,而我的查询是 int ,我认为 international 应该比 splint 排名更高,即使前者的得分(更高=更差)为10而后者为3。
所以我正在寻找(并且如果它不存在则会创建),是一个执行以下操作的库:
有没有人遇到过这样的事情?我意识到StackOverflow不是要求软件推荐的地方,但上面隐含的(不再是!)是:我是否正确地思考这个问题?
我在这个问题上找到了good paper (pdf)。一些注释和摘录:
仿射编辑距离函数为插入或删除序列分配相对较低的成本
Monger-Elkan距离函数(Monge& Elkan 1996),它是Smith-Waterman距离函数的一个仿射变体(Durban et al.1998),具有特定的成本参数
对于Smith-Waterman distance (wikipedia),“Smith-Waterman算法不是查看总序列,而是比较所有可能长度的片段,并优化相似性度量。”这是n-gram方法。
一个大致相似的指标,不是基于编辑距离模型,而是 Jaro metric(Jaro 1995; 1989; Winkler 1999年)。在记录链接文献中,使用此方法的变体获得了良好的结果,该变体基于两个字符串之间的共同字符的数量和顺序。
由于Winkler(1999)的变体也使用了最长公共前缀的长度P
(似乎主要用于短字符串)
对于文本完成目的,Monger-Elkan和Jaro-Winkler方法似乎最有意义。 Winkler对Jaro指标的补充有效地加重了单词的开头。而Monger-Elkan的仿射方面意味着完成一个单词的必要性(这只是一系列的补充)不会太过不喜欢它。
结论:
TFIDF 排名在几个基于令牌的距离中表现最佳 衡量和Monge和Elkan提出的调整的仿射间隙编辑距离指标在几个方面表现最佳 字符串编辑距离指标。一个令人惊讶的好距离 度量是一种快速的启发式方案,由Jaro提出,后来由Winkler扩展。 这几乎与Monge-Elkan计划一样,但是 比一个数量级更快。 一种简单的方法,结合TFIDF方法和 Jaro-Winkler将替换使用的确切令牌匹配 TFIDF具有基于Jaro的近似令牌匹配 温克勒计划。这种组合平均比Jaro-Winkler或TFIDF略好,并且偶尔表现得更好。它与几个最佳指标的学习组合在性能上也很接近 在本文中考虑过。
答案 0 :(得分:31)
我尝试使用像fuse.js这样的现有模糊库,并且发现它们很糟糕,所以我写了一个基本上像崇高的搜索。 https://github.com/farzher/fuzzysort
它允许的唯一错字是转置。它非常可靠(1k星,0期),非常快,并且可以轻松处理您的案例:
fuzzysort.go('int', ['international', 'splint', 'tinder'])
// [{highlighted: '*int*ernational', score: 10}, {highlighted: 'spl*int*', socre: 3003}]
答案 1 :(得分:18)
好问题!但我的想法是,不是试图修改Levenshtein-Demerau,你可能最好尝试不同的算法,或者将两种算法的结果组合/加权。
令我感到震惊的是,对于“起始前缀”的精确或接近匹配是Levenshtein-Demerau没有给予特别重视的东西 - 但是你明显的用户期望会。
我搜索了“比Levenshtein更好”,除其他外,发现了这个:
http://www.joyofdata.de/blog/comparison-of-string-distance-algorithms/
这提到了一些“字符串距离”措施。与您的要求特别相关的三个是:
最长公共子串距离:两个字符串中必须删除的最小符号数,直到生成的子串相同为止。
q-gram距离:两个字符串的N-gram向量之间的绝对差值之和。
Jaccard距离: 1分摊N-gram和所有观察到的N-gram的商数。
也许你可以使用这些指标的加权组合(或最小值),Levenshtein - 普通子串,普通N-gram或Jaccard都非常喜欢类似的字符串 - 或者也许只是尝试使用Jaccard?
根据列表/数据库的大小,这些算法可能会非常昂贵。对于我实现的模糊搜索,我使用可配置数量的N-gram作为DB的“检索关键字”,然后运行昂贵的字符串距离度量以按优先顺序对它们进行排序。
我在SQL中写了一些关于模糊字符串搜索的注释。参见:
答案 2 :(得分:14)
这是我用了几次的技术......它给出了非常好的结果。不会做你要求的一切。此外,如果列表很大,这可能会很昂贵。
get_bigrams = (string) ->
s = string.toLowerCase()
v = new Array(s.length - 1)
for i in [0..v.length] by 1
v[i] = s.slice(i, i + 2)
return v
string_similarity = (str1, str2) ->
if str1.length > 0 and str2.length > 0
pairs1 = get_bigrams(str1)
pairs2 = get_bigrams(str2)
union = pairs1.length + pairs2.length
hit_count = 0
for x in pairs1
for y in pairs2
if x is y
hit_count++
if hit_count > 0
return ((2.0 * hit_count) / union)
return 0.0
将两个字符串传递给string_similarity
,这将返回0
和1.0
之间的数字,具体取决于它们的相似程度。此示例使用Lo-Dash
用法示例....
query = 'jenny Jackson'
names = ['John Jackson', 'Jack Johnson', 'Jerry Smith', 'Jenny Smith']
results = []
for name in names
relevance = string_similarity(query, name)
obj = {name: name, relevance: relevance}
results.push(obj)
results = _.first(_.sortBy(results, 'relevance').reverse(), 10)
console.log results
另外......有一个fiddle
确保您的控制台已打开或您看不到任何内容:)
答案 3 :(得分:6)
你可以看看Atom的https://github.com/atom/fuzzaldrin/ lib。
它在npm上可用,具有简单的API,并且对我来说没问题。
> fuzzaldrin.filter(['international', 'splint', 'tinder'], 'int');
< ["international", "splint"]
答案 4 :(得分:2)
这是@InternalFX提供的解决方案,但是在JS中(我使用它是为了共享):
for(i : {1,2,3}) {
cout << i << "\n";
}
答案 5 :(得分:2)
我通过InternalFx修复了CoffeeScript bigram解决方案的问题,并使其成为通用的n-gram解决方案(您可以自定义克的大小)。
这是TypeScript,但是您可以删除类型注释,它也可以像普通JavaScript一样正常工作。
declare @sql varchar(8000), @varch varchar(100)
set @varch = format(GETDATE(),'MMddyyyyhhmmtt')
set @sql = 'master..xp_cmdshell ''sqlcmd -E -s"~" -W -Q "set NoCount On select PirTextValue from SSIB.dbo.ReoHdrFlatFile where CompanyId=179 union all Select PirTextValue from SSIB.dbo.ReoFlatFile " | findstr /V /C:"-" /B > D:\4.ReoProject\SolidFoundation\FoundationPIR\PIRTXTFile.txt'''
print @sql
exec(@sql)
示例:
/**
* Compares the similarity between two strings using an n-gram comparison method.
* The grams default to length 2.
* @param str1 The first string to compare.
* @param str2 The second string to compare.
* @param gramSize The size of the grams. Defaults to length 2.
*/
function stringSimilarity(str1: string, str2: string, gramSize: number = 2) {
function getNGrams(s: string, len: number) {
s = ' '.repeat(len - 1) + s.toLowerCase() + ' '.repeat(len - 1);
let v = new Array(s.length - len + 1);
for (let i = 0; i < v.length; i++) {
v[i] = s.slice(i, i + len);
}
return v;
}
if (!str1?.length || !str2?.length) { return 0.0; }
//Order the strings by length so the order they're passed in doesn't matter
//and so the smaller string's ngrams are always the ones in the set
let s1 = str1.length < str2.length ? str1 : str2;
let s2 = str1.length < str2.length ? str2 : str1;
let pairs1 = getNGrams(s1, gramSize);
let pairs2 = getNGrams(s2, gramSize);
let set = new Set<string>(pairs1);
let total = pairs2.length;
let hits = 0;
for (let item of pairs2) {
if (set.delete(item)) {
hits++;
}
}
return hits / total;
}
答案 6 :(得分:1)
(function (int) {
$("input[id=input]")
.on("input", {
sort: int
}, function (e) {
$.each(e.data.sort, function (index, value) {
if ( value.indexOf($(e.target).val()) != -1
&& value.charAt(0) === $(e.target).val().charAt(0)
&& $(e.target).val().length === 3 ) {
$("output[for=input]").val(value);
};
return false
});
return false
});
}(["international", "splint", "tinder"]))
答案 7 :(得分:1)
这是我模糊匹配的简短函数:
function fuzzyMatch(pattern, str) {
pattern = '.*' + pattern.split('').join('.*') + '.*';
const re = new RegExp(pattern);
return re.test(str);
}
答案 8 :(得分:1)
2019年11月更新。我发现保险丝有一些不错的升级。但是,我无法使用bool(即OR,AND等运算符),也无法使用API搜索界面来过滤结果。
我发现了 nextapps-de/flexsearch
:https://github.com/nextapps-de/flexsearch,我相信它远远超过了我尝试过的许多其他javascript搜索库,并且它支持{{ 1}},过滤搜索和分页。
您可以为搜索数据(即存储空间)输入一个javascript对象列表,并且该API的文档记录得很好:https://github.com/nextapps-de/flexsearch#api-overview
到目前为止,我已为近10,000条记录建立索引,而我的搜索几乎是立即进行的;即每次搜索所需的时间不明显。
答案 9 :(得分:0)
查看我的Google表格加载项Flookup并使用此功能:
Flookup (lookupValue, tableArray, lookupCol, indexNum, threshold, [rank])
参数详细信息为:
lookupValue
:您要查找的值tableArray
:您要搜索的表lookupCol
:您要搜索的列indexNum
:要从中返回数据的列threshold
:相似度百分比,低于该百分比时不应返回数据rank
:第n个最佳匹配项(即,如果您不喜欢第一个匹配项)这确实可以满足您的要求...尽管我不确定第2点。
在official website处了解更多信息。
答案 10 :(得分:0)
我已经爱上模糊匹配多年了,并且刚遇到这个线程。这里的谈话比大多数人更深入杂草,并且看起来涉及实施者。多年来,我已经用不同的语言编写了几个这样的算法,并希望将一些技巧传递给编写 JS 版本的任何人:
这真是太棒了,结合了 n-gram 的许多优点和最好的短字符串比较算法,例如 Jaro-Winkler。 (这就是我在我的 Monge-Elkan 代码中使用的。)几年前,我偶然发现了一篇论文,你可以在网上找到一份名为 Generalized Mongue-Elkan Method for Approximate Text String Comparison.结论是,不要使用算术平均值,而是使用二次平均值。我试用了它,它在搜索结果方面取得了显着改进,涵盖范围广泛的文本。
在各种源语言和文本类型中具有非常强大的高质量性能。如果您正在查看数据库,则可以在 Postgres 中将其实现为高质量、闪电般快速的索引 K-NN 搜索。它需要适当地排列几个不同的功能,但还不错。
无论如何,在拆分 n-gram 时,有不同的方法来处理前端填充。就像,如果你有一个传统的 n(q 或 k)3,那么你会像这样拆分“ander”吗 >
' a'
' an'
'and'
'nde'
'der'
'er '
'r '
或
' a'
' an'
'and'
'nde'
'der'
或
'and'
'nde'
'der'
本能地,我一直希望第一个列表效果最好,但实际上,它可能是第二个或第三个。值得尝试填充和窗口规则,并查看它们在您的上下文中的表现。很少有库提供对这种行为的控制,这将是一个很好的支持特性。提示。
答案 11 :(得分:-1)
模糊排序是一个JavaScript库,有助于从大量数据中进行字符串匹配。
以下代码将有助于在react.js中使用模糊排序。
通过npm安装模糊排序,
std::unique_ptr<base> dv{new derived()};
设置参考变量,
npm install fuzzysort
使用go()方法查找匹配的字符串
const fuzzysort = require('fuzzysort')
react.js中的完整演示代码
search(keyword, category) {
return fuzzysort.go(keyword, data[category]);
}
有关更多信息,请参见FuzzySort