为linq groupby编写自定义比较器

时间:2016-06-09 18:38:57

标签: linq iequalitycomparer

此示例再次是我实际问题的一个非常简化的版本,涉及linq分组的自定义比较器。我做错了什么?

以下代码生成以下结果 (1.2,0), (4.1,0),(4.1,0), (1.1,0),

然而,由于1.1和1.2是<&lt; 1&lt; 1.0分开。 (1.2,0),(1.1,0), (4.1,0),(4.1,0),

class Program
{
    static void Main(string[] args)
    {
        IEnumerable<Point> points = new List<Point> { 
            new Point(1.1, 0.0)
            , new Point(4.1, 0.0) 
            , new Point(1.2, 0.0)
            , new Point(4.1, 0.0)
        };

        foreach (var group in points.GroupBy(p => p, new PointComparer()))
        {
            foreach (var num in group)
                Console.Write(num.ToString() + ", ");

            Console.WriteLine();
        }

        Console.ReadLine();
    }
}

class PointComparer : IEqualityComparer<Point>
{
    public bool Equals(Point a, Point b)
    {
        return Math.Abs(a.X - b.X) < 1.0;
    }

    public int GetHashCode(Point point)
    {
        return point.X.GetHashCode()
            ^ point.Y.GetHashCode();
    }
}

class Point
{
    public double X;
    public double Y;

    public Point(double p1, double p2)
    {
        X = p1;
        Y = p2;
    }

    public override string ToString()
    {
        return "(" + X + ", " + Y + ")";
    }
}

2 个答案:

答案 0 :(得分:8)

使用相等比较器的分组算法(我认为所有LINQ方法)总是先比较哈希码,如果两个哈希码相等,则只执行$('.dropdown').click(function() { $('.show').toggle('.show'); }); 。如果在相等比较器中添加跟踪语句,则可以看到:

Equals

结果是:

class PointComparer : IEqualityComparer<Point>
{
    public bool Equals(Point a, Point b)
    {
        Console.WriteLine("Equals: point {0} - point {1}", a, b);
        return Math.Abs(a.X - b.X) < 1.0;
    }

    public int GetHashCode(Point point)
    {
        Console.WriteLine("HashCode: {0}", point);
        return point.X.GetHashCode()
            ^ point.Y.GetHashCode();
    }
}

仅执行具有相同哈希码HashCode: (1.1, 0) HashCode: (4.1, 0) HashCode: (1.2, 0) HashCode: (4.1, 0) Equals: point (4.1, 0) - point (4.1, 0) (1.1, 0), (4.1, 0), (4.1, 0), (1.2, 0), 的两个点。

现在你可以通过总是返回Equals作为哈希码来欺骗比较。如果这样做,输出将是:

0

现在已经执行了每对HashCode: (1.1, 0) HashCode: (4.1, 0) Equals: point (1.1, 0) - point (4.1, 0) HashCode: (1.2, 0) Equals: point (4.1, 0) - point (1.2, 0) Equals: point (1.1, 0) - point (1.2, 0) HashCode: (4.1, 0) Equals: point (4.1, 0) - point (4.1, 0) (1.1, 0), (1.2, 0), (4.1, 0), (4.1, 0), ,并且您已经进行了分组。

但是...

什么是“平等”?如果你添加另一个点Equals,你想在一个组中得到哪些点?使用符号(2.1, 0.0)进行模糊等式,我们有 -

1.1 ≈ 1.2
1.2 ≈ 2.1

这意味着1.1 !≈ 2.1 1.1永远不会在一个组中(他们的2.1永远不会通过)而它取决于点的顺序 Equals1.1是否与2.1分组。

所以你在这里滑坡。通过邻近聚类点远非微不足道。您正在进入cluster analysis

的领域

答案 1 :(得分:5)

不要忘记GetHashCode的影响。期望GetHashCode将始终为任何两个对象返回相同的值,每个Equals将返回true。如果你没有达到这个期望,你就会得到意想不到的结果。

具体来说,GroupBy可能使用哈希表之类的东西来允许它将项目组合在一起,而不必将每个项目与其他项目进行比较。如果GetHashCode返回的值不会将两个对象放入哈希表的同一个桶中,那么它会假设它们不相等,并且永远不会尝试调用Equals就可以了。

您会发现,当您试图找出GetHashCode的正确实施时,您尝试对对象进行分组存在根本问题。如果您的x值为1.01.62.2,那么您会期望什么? 1.02.2彼此之间的距离太远而无法归入同一群组,但1.6与其他两个点之间的距离足够接近它应与他们在同一群组中。因此,您的Equals方法会破坏相等的Transitive属性:

  

每当A = B且B = C时,则A = C

如果您尝试进行群集分组,则需要使用更加不同的数据结构和算法。如果你只是试图规范点数&#39;在某种程度上,您可能只能说points.GroupBy(p => (int)p.X)并完全避免使用相等比较器。