关于C#:相似的String算法

 2021-04-27 

Similar String algorithm

我正在寻找一种算法,或者至少是关于如何在两个或多个不同字符串中找到相似文本的操作理论...

很像这里提出的问题:查找具有相似文本的文章的算法,不同之处在于我的文本字符串永远只能是少数几个单词。

比如说我有一个字符串:
"走进湛蓝的天空"
我正在与以下两个字符串进行比较:
"颜色是天蓝色"和
"在湛蓝的天空中"

我正在寻找一种算法,该算法可用于匹配两个文本,并确定它们之间的匹配程度。就我而言,拼写和标点符号将很重要。我不希望他们影响发现真实文本的能力。在上面的示例中,如果颜色参考存储为" \\'天蓝色\\'",我希望它仍然能够匹配。但是,列出的第三个字符串应与第二个字符串更好匹配,以此类推。

我确定Google之类的地方可能会使用与"您的意思是:"功能类似的内容...

*编辑*
在与朋友交谈时,他与一个就此主题撰写论文的人一起工作。我想我可以和所有阅读此书的人分享它,因为其中描述了一些非常好的方法和过程...

这是他论文的链接,希望对阅读此问题以及类似字符串算法主题的人有所帮助。


Levenshtein距离不会完全起作用,因为您希望允许重新排列。我认为您最好的选择是找到以单词的成本为代价的勒文施泰因距离的最佳重排。

要找到重新安排的成本,有点像煎饼分类问题。因此,您可以置换每个单词组合(过滤出精确匹配项)以及其他字符串的每个组合,以尝试最小化每个单词对上的置换距离和Levenshtein距离的组合。

编辑:
现在,我可以发表一个简单的示例(所有"最佳"猜测都在检查中,而实际上并未运行算法):

1
2
3
4
5
6
original strings             | best rearrangement w/ lev distance per word
Into the clear blue sky      |    Into the c_lear blue sky
The color is sky blue        |    is__ the colo_r blue sky

R_dist = dist( 3 1 2 5 4 ) --> 3 1 2 *4 5* --> *2 1 3* 4 5 --> *1 2* 3 4 5 = 3  
L_dist = (2D+S) + (I+D+S) (Total Subsitutions: 2, deletions: 3, insertion: 1)

(请注意,所有翻转都包含该范围内的所有元素,并且我使用Xi-Xj = /-1的范围)

其他示例

1
2
3
4
5
6
original strings             | best rearrangement w/ lev distance per word
Into the clear blue sky      |   Into the clear blue sky
In the blue clear sky        |   In__ the clear blue sky

R_dist = dist( 1 2 4 3 5 ) -->  1 2 *3 4* 5  = 1
L_dist = (2D) (Total Subsitutions: 0, deletions: 2, insertion: 0)

并显示三个...的所有可能组合...

1
2
3
4
5
The color is sky blue         |    The colo_r is sky blue
In the blue clear sky         |    the c_lear in sky blue

R_dist = dist( 2 4 1 3 5 ) --> *2 3 1 4* 5 --> *1 3 2* 4 5 --> 1 *2 3* 4 5 = 3
L_dist = (D+I+S) + (S) (Total Subsitutions: 2, deletions: 1, insertion: 1)

无论如何,使成本函数成为第二选择是最低成本,这就是您所期望的!


一种确定"总体相似性而不考虑顺序"的度量的方法是使用某种基于压缩的距离。基本上,大多数压缩算法(例如gzip)的工作方式是沿着字符串扫描以查找较早出现的字符串段-每当找到这样的段时,都将其替换为(偏移量,长度)对,以标识较早使用的细分。您可以使用度量两个字符串的压缩程度来检测它们之间的相似性。

假设您有一个函数string comp(string s),该函数返回了s的压缩版本。然后,可以将以下表达式用作两个字符串st之间的"相似性得分":

1
len(comp(s)) + len(comp(t)) - len(comp(s . t))

其中.被视为串联。这个想法是,您正在通过首先查看s来衡量可以进一步压缩t多少。如果s == t,则len(comp(s . t))几乎不会比len(comp(s))大,并且您会获得高分,而如果它们完全不同,则len(comp(s . t))将会非常接近len(comp(s) + comp(t)),并且您会得到分数接近零。中级相似度产生中级分数。

实际上,以下公式是对称的,因此甚至更好(即,分数不随哪个字符串为s和哪个字符串为t而变化):

1
2 * (len(comp(s)) + len(comp(t))) - len(comp(s . t)) - len(comp(t . s))

该技术起源于信息论。

优点:良好的压缩算法已经可用,因此您无需执行大量编码,并且它们以线性时间(或差不多)运行,因此速度很快。相比之下,涉及单词所有排列的解决方案的单词数量将成倍增长(尽管可以肯定的是,在您的情况下这可能不是问题,因为您说过知道只有少数单词)。


您可能想研究生物学家用来比较DNA序列的算法,因为它们必须处理许多相同的事情(块可能会丢失,被插入或刚刚移到基因组中的其他位置)字符串。

史密斯-沃特曼算法将是一个可能运行得很好的示例,尽管它对于您的使用而言可能太慢了。不过,可能会为您提供一个起点。


"编辑距离"是一种方法(尽管这可能更适合拼写检查类型的算法),即计算将一个字符串转换为另一个字符串所需的编辑次数。在这里可以找到一种常见的技术:

http://en.wikipedia.org/wiki/Levenshtein_distance


我有一个类似的问题,我需要获取字符串中相似字符的百分比。它需要精确的序列,因此例如" hello sir"和" sir hello"在比较时需要给我五个相同的字符,在这种情况下,它们将是两个" hello"。然后,它需要两个字符串中最长的一个字符串的长度,然后给我一定百分比的相似程度。这是我想出的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
int compare(string a, string b){
   return(a.size() > b.size() ? bigger(a,b) : bigger(b,a));
}



int bigger(string a, string b){



int maxcount = 0, currentcount = 0;//used to see which set of concurrent characters were biggest

for(int i = 0; i < a.size(); ++i){

    for(int j = 0; j < b.size(); ++j){

        if(a[i+j] == b[j]){

         ++currentcount;

         }

        else{

            if(currentcount > maxcount){

             maxcount = currentcount;

             }//end if

             currentcount = 0;

            }//end else

        }//end inner for loop

    }//end outer for loop


   return ((int)(((float)maxcount/((float)a.size()))*100));
}

我无法在此处标记两个答案,因此我将回答并标记自己的答案。在大多数情况下,Levenshtein距离似乎是正确的方法。但是,也值得一提j_random_hackers答案。我已经使用LZMA的实现来测试他的理论,事实证明这是一个合理的解决方案。在我最初的问题中,我正在寻找一种用于短字符串(2到200个字符)的方法,Levenshtein距离算法将在此方法中起作用。但是,问题中没有提到需要比较两个(较大的)字符串(在这种情况下,是中等大小的文本文件)并进行快速检查以查看两者的相似程度。我相信这种压缩技术会很好地工作,但是我还没有研究它,就样本数据的大小和所讨论的操作的速度/成本而言,哪一种方法比另一种方法更好。我认为,对于任何想像我在这里解决类似的字符串折磨一样的人,此问题的很多答案都是有价值的,并且值得一提。谢谢大家的出色回答,我希望它们也可以用于为他人提供良好的服务。


还有另一种方式。使用卷积的模式识别。图像A通过傅立叶变换运行。图像B也。现在将F(A)叠加在F(B)上,然后将其变换回给您,得到带有一些白色斑点的黑色图像。这些斑点表示A与B强烈匹配的地方。点的总和将指示总体相似性。不知道如何对字符串运行FFT,但我很确定它会起作用。


困难在于语义上匹配字符串。

您可以根据字符串的词汇属性生成某种值。例如他们的机器人有蓝色和天空,它们在同一句子中,等等,等等。但是它不能处理"天空的牛仔是蓝色"的情况,也不能处理其他使用相同单词的奇数英语结构,但是您需要解析英语语法...

要执行除词汇相似性之外的任何事情,您需要研究自然语言处理,并且不会有一个单一的算法可以解决您的问题。


可能的方法:

针对参考字符串中所有单词的组合,使用字符串关键字" word1 | word2"构建字典。单个组合可能会发生多次,因此Dictionary的值应为数字列表,每个数字代表参考字符串中单词之间的距离。

执行此操作时,将在此处重复:对于每个" word1 | word2"字典条目,将有一个" word2 | word1"条目,具有相同的距离值列表,但取反。

对于比较字符串中的每个单词组合(单词1和2,单词1和3,单词2和3等),请检查参考字符串中的两个键(word1 | word2和word2 | word1),并在当前字符串中找到最接近距离的值。将当前距离与最接近距离之间的差的绝对值与计数器相加。

如果单词之间的最接近参考距离与比较字符串的方向相反(word2 | word1),则可能要使其权重小于两个字符串中最接近的值沿相同方向的方向。

完成后,将总和除以比较字符串中单词数的平方。

这应该提供一个十进制值,表示每个单词/短语与原始字符串中某个单词/短语的接近程度。

当然,如果原始字符串较长,则不会考虑这一点,因此可能有必要计算这两个方向(使用一个方向作为参考,然后使用另一个方向)并对它们求平均值。

我对此绝对没有代码,我可能只是重新发明了一个非常粗糙的轮子。 YMMV。