我在使用PHP levenshtein函数比较字符串方面取得了一些成功。
但是,对于包含已交换位置的子串的两个字符串,算法会将这些字符串计为全新的子字符串。
例如:
levenshtein("The quick brown fox", "brown quick The fox"); // 10 differences
被视为不太常见而不是:
levenshtein("The quick brown fox", "The quiet swine flu"); // 9 differences
我更喜欢一种算法,它看到前两个更相似。
我怎样才能提出一个比较函数,它可以识别将位置切换为与编辑不同的子串?
我想到的一种可能的方法是在比较之前将字符串中的所有单词按字母顺序排列。这使得单词的原始顺序完全脱离了比较。然而,这样做的一个缺点是,只更改一个单词的第一个字母可能会造成比单个字母更改所造成的更大的中断。
我想要实现的是比较两个关于自由文本字符串的人的事实,并确定这些事实表明相同事实的可能性。事实可能是有人上学的学校,例如雇主或出版商的名字。两个记录可能有相同的学校拼写不同,单词顺序不同,额外的单词等,所以如果我们要好好猜测他们指的是同一所学校,匹配必须有些模糊。到目前为止,它在拼写错误方面表现得非常好(我使用的是一种类似于metaphone的phoenetic算法),但是如果你改变学校中常见的单词顺序则非常糟糕:“xxx college”vs “xxx学院”。
答案 0 :(得分:20)
使用N-grams,支持整个文字中的多字符转置。
一般的想法是你将所讨论的两个字符串分成所有可能的2-3个字符子串(n-gram),并将两个字符串之间的共享n-gram数量作为它们的相似性度量。然后可以通过将共享数除以较长字符串中的n-gram总数来对其进行归一化。这很难计算,但相当强大。
对于例句:
A. The quick brown fox
B. brown quick The fox
C. The quiet swine flu
A和B分享 18 2克
A和C只分享 8 2克
20 总数。
Gravano et al. paper已对此进行了详细讨论。
一个不那么琐碎的替代方案,但以信息理论为基础的是使用术语term frequency–inverse document frequency (tf-idf)来权衡令牌,构造句子向量,然后使用cosine similarity作为相似性度量。
算法是:
关于其他答案。 Damerau–Levenshtein修改仅支持两个相邻字符的转置。 Metaphone旨在匹配听起来相同但不相同的单词 用于相似性匹配。
答案 1 :(得分:9)
很容易。只需在单词上使用Damerau-Levenshtein距离而不是字母。
答案 2 :(得分:6)
爆炸空间,对数组进行排序,内爆,然后进行Levenshtein。
答案 3 :(得分:3)
您也可以试试这个。 (只是一个额外的建议)
$one = metaphone("The quick brown fox"); // 0KKBRNFKS
$two = metaphone("brown quick The fox"); // BRNKK0FKS
$three = metaphone("The quiet swine flu"); // 0KTSWNFL
similar_text($one, $two, $percent1); // 66.666666666667
similar_text($one, $three, $percent2); // 47.058823529412
similar_text($two, $three, $percent3); // 23.529411764706
这将显示第1和第2个比第1和第3,第2和第3更相似。
答案 4 :(得分:3)
我一直在拼写检查器中实施levenshtein。
你所要求的是将换位计为1次编辑。
如果您只想计算一个单词的换位,这很容易。然而,对于2个或更多单词的转换,算法的添加是最坏情况!(max(wordorder1.length(), wordorder2.length()))
。将非线性子算法添加到已经二次的算法中并不是一个好主意。
这就是它的工作原理。
if (wordorder1[n] == wordorder2[n-1])
{
min(workarray[x-1, y] + 1, workarray[x, y-1] + 1, workarray[x-2, y-2]);
}
else
{
min(workarray[x-1, y] + 1, workarray[x, y-1] + 1);
}
只是为了触摸换位。如果你想要所有的换位,你必须从那个位置比较
为每个位置工作1[n] == 2[n-2].... 1[n] == 2[0]....
所以你明白他们为什么不在标准方法中包含它。
答案 5 :(得分:1)
选择this answer并进行以下更改:
void match(trie t, char* w, string s, int budget){
if (budget < 0) return;
if (*w=='\0') print s;
foreach (char c, subtrie t1 in t){
/* try matching or replacing c */
match(t1, w+1, s+c, (*w==c ? budget : budget-1));
/* try deleting c */
match(t1, w, s, budget-1);
}
/* try inserting *w */
match(t, w+1, s + *w, budget-1);
/* TRY SWAPPING FIRST TWO CHARACTERS */
if (w[1]){
swap(w[0], w[1]);
match(t, w, s, budget-1);
swap(w[0], w[1]);
}
}
这是用于trie中的字典搜索,但是为了匹配单个单词,它是相同的想法。你正在进行分支,并且在任何时候,你可以做任何你喜欢的改变,只要你付出代价。
答案 6 :(得分:1)
消除两个字符串之间的重复单词,然后使用Levenshtein。
答案 7 :(得分:1)
我相信这是使用vector-space search engine的一个主要示例。
在这种技术中,每个文档基本上都成为一个具有与整个语料库中不同单词一样多的维度的向量;然后类似的文档占据该向量空间中的相邻区域。这个模型的一个不错的属性是查询也只是文档:要回答查询,您只需计算它们在向量空间中的位置,结果就是您可以找到的最接近的文档。我确信那里有PHP的“即将推出”的解决方案。
从矢量空间模糊化结果,您可以考虑使用词干/类似的自然语言处理技术,并使用levenshtein为您的整体词汇中出现的类似单词构建辅助查询。
答案 8 :(得分:0)
如果第一个字符串是A而第二个字符串是B:
示例:
A: The quick brown fox
B: Quick blue fox the
B*: the Quick blue fox
您可以通过多次传递来改进第2步,首先只找到完全匹配,然后找到A中没有B *中的伴侣的单词的紧密匹配,然后是较少的匹配,等等。 / p>