为linq groupby编写自定义比较器
同样,这个示例是我实际问题的一个非常简化的版本,涉及用于linq分组的自定义比较器。我做错了什么 下面的代码生成下面的结果 (1.2, 0), (4.1, 0), (4.1, 0), (1.1,0) 但是,由于1.1和1.2的间隔小于1.0,因此我预计会出现以下情况。 (1.2, 0), (1.1, 0), (4.1,0),(4.1,0)为linq groupby编写自定义比较器,linq,iequalitycomparer,Linq,Iequalitycomparer,同样,这个示例是我实际问题的一个非常简化的版本,涉及用于linq分组的自定义比较器。我做错了什么 下面的代码生成下面的结果 (1.2, 0), (4.1, 0), (4.1, 0), (1.1,0) 但是,由于1.1和1.2的间隔小于1.0,因此我预计会出现以下情况。 (1.2, 0), (1.1, 0), (4.1,0),(4.1,0) 类程序 { 静态void Main(字符串[]参数) { IEnumerable points=新列表{ 新点(1.1,0.0) ,新点(4.1,0.0) ,
类程序
{
静态void Main(字符串[]参数)
{
IEnumerable points=新列表{
新点(1.1,0.0)
,新点(4.1,0.0)
,新点(1.2,0.0)
,新点(4.1,0.0)
};
foreach(points.GroupBy中的var组(p=>p,new PointComparer())
{
foreach(组中的var num)
Write(num.ToString()+“,”);
Console.WriteLine();
}
Console.ReadLine();
}
}
类指针比较器:IEqualityComparer
{
公共布尔等于(a点,b点)
{
返回Math.Abs(a.X-b.X)<1.0;
}
public int GetHashCode(Point-Point)
{
返回点.X.GetHashCode()
^point.Y.GetHashCode();
}
}
类点
{
公共双X;
公共双Y;
公共点(双p1,双p2)
{
X=p1;
Y=p2;
}
公共重写字符串ToString()
{
返回“(“+X+”、“+Y+”)”;
}
}
不要忘记GetHashCode
的效果。对于任何两个对象,GetHashCode
总是会返回相同的值,因为每个Equals
都会返回true。如果你没有达到预期,你会得到意想不到的结果
具体地说,GroupBy
可能使用类似哈希表的东西将项目分组在一起,而无需将每个项目与每个其他项目进行比较。如果GetHashCode
返回一个值,但该值不会最终将两个对象放入哈希表的同一个存储桶中,它将假定它们不相等,并且永远不会尝试对它们调用Equals
当您试图找出GetHashCode
的正确实现时,您会发现如何对对象进行分组存在一个基本问题。如果点的x值分别为1.0
、1.6
和2.2
,您会期望什么1.0和2.2
距离太远,无法归入同一组,但1.6
距离其他两个点足够近,因此应与它们位于同一组。因此,您的Equals
方法打破了相等的属性:
只要A=B和B=C,那么A=C
如果您试图进行集群分组,则需要使用更不同的数据结构和算法。如果您只是尝试对点的位置进行某种规格化,那么您可以只说points.GroupBy(p=>(int)p.X)
,完全避免使用相等比较器。分组算法(我认为所有LINQ方法都是如此)使用相等比较器总是首先比较哈希代码,并且只有在两个哈希代码相等时才执行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();
}
}
仅对哈希代码相等的两点执行Equals
现在,您可以通过始终将0
作为哈希代码返回来欺骗比较。如果这样做,输出将是:
HashCode:(1.1,0)
哈希代码:(4.1,0)
等于:点(1.1,0)-点(4.1,0)
哈希代码:(1.2,0)
等于:点(4.1,0)-点(1.2,0)
等于:点(1.1,0)-点(1.2,0)
哈希代码:(4.1,0)
等于:点(4.1,0)-点(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
永远不会在一组中(它们的等于永远不会通过),这取决于点的顺序1.1
或2.1
是否与1.2
分组
所以你在这里走下坡路。通过邻近性对点进行聚类绝非易事。您正在进入。我认为您无法使用group by作为群集点的解决方案。一个原因是GetHashcode必须为相等的项返回相同的哈希值。我知道这里没有这样做,但需要注意的是:不要使用PointComparer.Default
而不是new PointComparer()
——不管文档怎么说,它不会创建一个新的指针比较器
,而是创建一个ObjectEqualityComparer`1
。这正是我所看到的,我的GetHasCode对这个问题有重大影响。我对它所做的任何更改都会导致输出的更改。我的GetHashCode方法应该是什么样子?@DustyB:请看我更新的答案。你目前对两项“相等”的定义是站不住脚的。你必须想出一个更具体的想法来对项目进行分组,或者使用不同的数据结构和算法,基于聚类而不是相等。我的实际问题是使用3D点,我想对它们进行聚类,使10个单位内的点和同一平面上的点组合在一起。用自定义比较器做这件事可行吗?@DustyB:不可靠。如果A点和B点之间的距离在10个单位以内,B点和C点之间的距离在10个单位以内,但A点与C点之间的距离不在10个单位以内,那么它们是否都应该在一起?无论您如何回答这个问题,您选择的任何纯粹基于相等的分组算法都将无法在以下条件下生成您想要的输出
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();
}
}