决定2个字符串是否“足够相似”的好指标是什么?

时间:2011-12-09 20:53:45

标签: java string-matching levenshtein-distance similarity

我正在研究一个非常粗略的初稿算法,以确定2个字符串的相似程度。我还使用Levenshtein Distance来计算字符串之间的编辑距离。

我目前正在做的是基本上采用编辑总数并将其除以较大字符串的大小。如果该值低于某个阈值,当前随机设置为25%,则它们“足够相似”。

然而,这完全是武断的,我认为这不是计算相似性的好方法。是否有某种数学方程式或概率/统计方法来获取Levenshtein距离数据并使用它来说“是的,根据所做的编辑次数和字符串的大小,这些字符串是否足够相似”?

此外,关键是我使用任意阈值,我宁愿不这样做。如何计算此阈值而不是分配它以便我可以安全地说2个字符串“足够相似”

更新

我正在比较代表Java堆栈跟踪的字符串。我想这样做的原因是通过相似性对一堆给定的堆栈跟踪进行分组,并将其用作过滤器来对“东西”进行排序:)这种分组对于我无法公开分享的更高级别的原因很重要。


到目前为止,我的算法(伪代码)大致如下:

/*
 * The input lists represent the Strings I want to test for similarity. The
 * Strings are split apart based on new lines / carriage returns because Java
 * stack traces are not a giant one-line String, rather a multi-line String.
 * So each element in the input lists is a "line" from its stack trace.
 */
calculate similarity (List<String> list1, List<String> list2) {

    length1 = 0;
    length2 = 0;
    levenshteinDistance = 0;

    iterator1 = list1.iterator();
    iterator2 = list2.iterator();

    while ( iterator1.hasNext() && iterator2.hasNext() ) {

        // skip blank/empty lines because they are not interesting
        str1 = iterator1.next();    length1 += str1.length();
        str2 = iterator2.next();    length2 += str2.length();

        levensteinDistance += getLevenshteinDistance(str1, str2);
    }

    // handle the rest of the lines from the iterator that has not terminated

    difference = levenshteinDistance / Math.max(length1, length2);

    return (difference < 0.25) ? true : false; // <- arbitrary threshold, yuck!
}

4 个答案:

答案 0 :(得分:20)

如何使用余弦相似度?这是评估两个文本之间相似性的一般技术。它的工作原理如下:

从两个字符串中取出所有字母构建一个这样的表:

Letter | String1 | String2

这可以是一个简单的哈希表或其他任何内容。

在字母列中,将每个字母和字符串列中的频率放在该字符串中(如果字母中没有出现字母,则值为0)。

它被称为余弦相似性,因为您将两个字符串列中的每一个都解释为向量,其中每个组件都是与字母关联的数字。接下来,计算向量之间“角度”的余弦值:

C = (V1 * V2) / (|V1| * |V2|)

分子是点积,即相应分量的乘积之和,分母是向量大小的乘积。

C与1的接近程度给出了字符串的相似程度。

这可能看起来很复杂,但只要你理解了这个想法,它只需要几行代码。

让我们看一个例子:考虑字符串

s1 = aabccdd
s2 = ababcd

表格如下:

Letter a b c d
s1     2 1 2 2
s2     2 2 1 1

因此:

C = (V1 * V2) / (|V1| * |V2|) = 
(2 * 2 + 1 * 2 + 2 * 1 + 2 * 1) / (sqrt(13) * sqrt(10)) = 0.877

所以他们“非常”相似。

答案 1 :(得分:4)

堆栈跟踪的格式适合解析。我只是使用解析库解析堆栈跟踪,然后您可以提取您想要比较的任何语义内容。

当字符串没有按照您的预期进行比较时,相似性算法将变得更慢且难以调试。

答案 2 :(得分:2)

这是我对此的看法 - 只是一个需要考虑的长篇故事,而不一定是你问题的答案:

我过去做过类似的事情,我会尝试通过简单地重新排列句子来确定某人是否在抄袭,同时保持同样的信息。

<1>小孩应该在我们吃晚餐时玩耍。
2“我们一起吃晚饭,孩子们应该玩” 3“我们玩的时候应该吃孩子”

所以levenshtein在这里没什么用,因为它是线性的,每一个都会有很大不同。标准差异将通过测试,学生将逃脱犯罪。

所以我打破句子中的每个单词并将句子重新组合为数组,然后相互比较以首先确定每个数组中是否存在单词,以及它与最后一个单元的关系。然后每个单词将检查数组中的下一个单词以确定是否存在连续单词,就像在第1行和第2行上方的示例句子中一样。 因此,如果有连续的单词,我会为每个数组组成一个共同的每个序列的字符串,然后尝试找到剩余单词的差异。剩下的单词越少,它们就越有可能成为填充物,使其看起来不那么剽窃。

“我们吃晚饭的时候,我觉得孩子们应该玩”

然后“我认为”被评估并被认为是基于关键词词典的填充 - 这部分很难在这里描述。

这是一个复杂的项目,它做的不仅仅是我所描述的,而不是我可以轻松分享的一小段代码,但上面的想法并不难以复制。

祝你好运。我对其他SO成员对你的问题有什么看法感兴趣。

答案 3 :(得分:1)

由于Levenshtein距离永远不会超过较长字符串的长度,我当然会将分母从(length1 + length2)更改为Math.max(length1, length2)。这会将度量标准化为0到1之间。

现在,根据提供的信息,根据您的需求回答“足够相似”的内容是不可能的。我个人试图避免像0.25截止的步进功能,更喜欢已知间隔的连续值。也许将连续的“相似性”(或“距离”)值提供给更高级别的算法而不是将这些值转换为二进制算法会更好?