Dice Sorensen在不使用Intersect方法的情况下计算Bigrams的距离误差

时间:2016-04-15 15:54:40

标签: c# .net string distance intersection

我一直在编写一个对象来计算两个字符串之间的DiceSorensen距离。操作的逻辑并不那么困难。您可以计算字符串中存在多少两个字母对,将其与第二个字符串进行比较,然后执行此等式 2(x与y相交)/(| x |。| y |)

其中| x |和| y |是x&中的二元素元素的数量年。可以在此处找到参考文献以进一步明确https://en.wikipedia.org/wiki/S%C3%B8rensen%E2%80%93Dice_coefficient

所以我试着在不同的地方查找如何在线执行代码,但我遇到的每种方法都使用两个列表之间的'Intersect'方法,据我所知这不起作用,因为如果你有bigram已存在的字符串不会添加另一个字符串。例如,如果我有一个字符串 'AAAA' 我希望有3'aa'双胞胎,但Intersect方法只生成一个,如果我在这个假设上不正确请告诉我因为我想知道为什么这么多人使用交叉方法。我的假设是基于MSDN网站https://msdn.microsoft.com/en-us/library/bb460136(v=vs.90).aspx

所以这是我制作的代码

public static double SorensenDiceDistance(this string source, string target)
{
    // formula 2|X intersection Y|
    //         --------------------
    //          |X|     +     |Y|

    //create variables needed
    List<string> bigrams_source = new List<string>();
    List<string> bigrams_target = new List<string>();

    int source_length;
    int target_length;
    double intersect_count = 0;
    double result = 0;

    Console.WriteLine("DEBUG: string length source is " + source.Length);

    //base case
    if (source.Length == 0 || target.Length == 0)
    {
        return 0;
    }

    //extract bigrams from string 1
    bigrams_source = source.ListBiGrams();
    //extract bigrams from string 2
    bigrams_target = target.ListBiGrams();

    source_length = bigrams_source.Count();
    target_length = bigrams_target.Count();
    Console.WriteLine("DEBUG: bigram counts are source: " + source_length + " . target length : " + target_length);
    //now we have two sets of bigrams compare them in a non distinct loop

    for (int i = 0; i < bigrams_source.Count(); i++)
    {
        for (int y = 0; y < bigrams_target.Count(); y++)
        {
            if (bigrams_source.ElementAt(i) == bigrams_target.ElementAt(y))
            {
                intersect_count++;
                //Console.WriteLine("intersect count is :" + intersect_count);
            }
        }
    }
    Console.WriteLine("intersect line value : " + intersect_count);

    result = (2 * intersect_count) / (source_length + target_length);

    if (result < 0)
    {
        result = Math.Abs(result);
    }

    return result;
}

在代码的某处你可以看到我调用了一个名为listBiGrams的方法,这就是它的外观

public static List<string> ListBiGrams(this string source)
{
    return ListNGrams(source, 2);
}

public static List<string> ListTriGrams(this string source)
{
    return ListNGrams(source, 3);
}

public static List<string> ListNGrams(this string source, int n)
{
    List<string> nGrams = new List<string>();

    if (n > source.Length)
    {
        return null;
    }
    else if (n == source.Length)
    {
        nGrams.Add(source);
        return nGrams;
    }
    else
    {
        for (int i = 0; i < source.Length - n; i++)
        {
            nGrams.Add(source.Substring(i, n));
        }

        return nGrams;
    }
}

所以我逐步了解代码是 1)传入字符串 2)0长度检查 3)创建列表并将bigrams传递给它们 4)获取每个二元组列表的长度 5)嵌套循环检查源位置[i]对目标字符串中的每个bigram然后递增i直到没有更多的源列表进行检查 6)执行上面提到的从维基百科中获得的等式 7)如果结果为负数Math.Abs​​它返回一个肯定的结果(但是我知道结果应该在0和1之间,这就是让我知道我做错了什么)

我使用的源字符串是source =“这不是正确的字符串”,目标字符串是,target =“这是一个正确的字符串”

我得到的结果是-0.090909090908

我确定(99%)我所缺少的东西就像某个地方错误计算的长度或计数错误。如果有人能指出我做错了什么,我会非常感激。谢谢你的时间!

1 个答案:

答案 0 :(得分:0)

这看起来像是家庭作业,但字符串上的这种相似度量对我来说是新的,所以我看一看。

Algorith implementation in various languages

您可能会注意到C#版本使用HashSet并利用IntersectWith方法。

  

集合是一个不包含重复元素的集合,其集合   元素没有特别的顺序。

这解决了你的字符串'aaaa'难题。那里只有一个二元组。

My naive implementation on Rextester

如果您更喜欢Linq,那么我建议Enumerable.DistinctEnumerable.UnionEnumerable.Intersect。这些应该很好地模仿HashSet的重复删除功能。

还发现这个用Scala写的好StringMetric framework

相关问题