列表的GroupBy具有容差不起作用

我对C#的Groupby有疑问。

我制作了如下所示的List

 List testList = new List(); testList.Add(1); testList.Add(2.1); testList.Add(2.0); testList.Add(3.0); testList.Add(3.1); testList.Add(3.2); testList.Add(4.2); 

我想将这些数字列表分组如下:

 group 1 => 1 group 2 => 2.1 , 2.0 group 3 => 3.0 , 3.1 , 3.2 group 4 => 4.2 

所以,我写了这样的代码:

 var testListGroup = testList.GroupBy(ele => ele, new DoubleEqualityComparer(0.5)); 

DoubleEqualityComparer定义如下:

 public class DoubleEqualityComparer : IEqualityComparer { private double tol = 0; public DoubleEqualityComparer(double Tol) { tol = Tol; } public bool Equals(double d1,double d2) { return EQ(d1,d2, tol); } public int GetHashCode(double d) { return d.GetHashCode(); } public bool EQ(double dbl, double compareDbl, double tolerance) { return Math.Abs(dbl - compareDbl) < tolerance; } } 

然而, GroupBy子句不像这样工作:

 group 1 => 1 group 2 => 2.1 group 3 => 2.0 group 4 => 3.0 group 5 => 3.1 group 6 => 3.2 group 7 => 4.2 

我不知道问题是什么。 如果有问题,请告诉我,解决方案。

使用简单的Math.Floor来获得较低的数字范围,以便5.8不应被视为6。

 List testList = new List(); testList.Add(1); testList.Add(2.1); testList.Add(2.0); testList.Add(3.0); testList.Add(3.1); testList.Add(3.2); testList.Add(4.2); testList.Add(5.8); testList.Add(5.5); var testListGroup = testList.GroupBy(s => Math.Floor(s)).ToList(); 

在这些类型的情况下,调试器是你的朋友。 在Equals方法上设置一个断点。 您会注意到DoubleEqualityComparer类的Equals方法没有被命中。

Linq扩展方法依赖于GetHashCode进行相等比较。 由于GetHashCode方法没有为列表中的双精度返回等效哈希值,因此不会调用Equals方法。

每个GetHashCode方法都应该是primefaces执行的,并且应该为任何两个相等的比较返回相同的int值。

这是一个工作示例,但不一定建议取决于您对此比较器的使用情况。

 public int GetHashCode(double d) { return 1; } 

您可以使用以下代码示例进行分组,

 var list = testList.GroupBy(s => Convert.ToInt32(s) ).Select(group => new { Key = group.Key, Elements = group.ToList() }); //OutPut //group 1 => 1 //group 2 => 2.1 , 2 //group 3 => 3 , 3.1 , 3.2 //group 4 => 4.2 

代码说明当我们对只有一个数据列的列表应用GroupBy ,它通过查看相同的内容进行分组。 举个例子,你认为你有字符串列表(foo1,foo2,foo3,foo1,foo1,foo2)。 然后它分成三个独立的组,由foo1,foo2和foo3领导。

但在这种情况下你找不到任何相同的内容(1.0,2.1,2.2,2.3,3.1,3.2 ……)所以我们应该做的是将它们作为相同的内容。 当我们将它们转换为int它给出(1,2,2,2,3,3 ……)。 然后我们可以轻松地将其分组。

您的GetHashCode方法应为数字返回相同的值,该值应为“相等”。

EqualityComparer分两步工作:

  1. 检查GetHashCode如果尚未处理具有此哈希码的值,则此值将进入新的单个组

  2. 如果获得了具有此哈希码的值 – 则检查Equals方法的结果。 如果是 – 将当前元素添加到现有组,否则将其添加到新组。

在您的情况下,每个double返回不同的哈希码,因此方法Equals不会被调用。

因此,如果您不关心处理时间,可以像@FirstCall建议的那样在GetHashCode方法中简单地返回常量值。 如果你关心它,我建议修改你的方法如下:

 public int GetHashCode(double d) { return Math.Round(d).GetHashCode(); } 

Math.Round应该在公差= 0.5时正确工作,对于另一个公差值,您应该改进这一点。

我建议你阅读这篇博文,以熟悉IEqualityComparerLinq

使用较少代码量的最简单方法总是从GetHashCode返回常量值 – 它适用于任何容差值,但是,正如我所写,它是对大量数据的非常低效的解决方案。

这里的每个人都在讨论你的代码有什么问题,但实际上你可能遇到了更糟糕的问题。

如果你真的想要像你的标题所说的那样使用容差进行分组,而不是像这些答案那样按整数部分假设(并且你的测试数据支持), GroupBy不支持这种情况。

GroupBy要求等价关系 – 你的等式比较器必须建立它

  • x == x表示所有x
  • 如果对于所有xyx == yy == x
  • 如果对于所有xyzx == yy == zx == z z

“在彼此的0.5之内”匹配前两个点,但不是第三个。 0接近0.4,0.4接近0.8,但0不接近0.8。 如果输入0,0.4和0.8,您会期望哪些组?

您的问题是您的GetHashCode实现,它必须为您认为相等的所有内容返回相等的值。 因此,对于应该转到同一组的两个不同的值d1d2 ,该方法应该返回相同的哈希码。 为此,将给定数字四舍五入到最近的整数,然后计算其哈希码:

 public int GetHashCode(double d) { return Convert.ToInt32(d).GetHashCode(); } 

计算完哈希码之后 ,实际完成了Equal 。 在当前情况下, GetHashCode返回的哈希值是不同的,因此根本不会执行Equals