列表的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分两步工作:
-
检查
GetHashCode
如果尚未处理具有此哈希码的值,则此值将进入新的单个组 -
如果获得了具有此哈希码的值 – 则检查
Equals
方法的结果。 如果是 – 将当前元素添加到现有组,否则将其添加到新组。
在您的情况下,每个double
返回不同的哈希码,因此方法Equals
不会被调用。
因此,如果您不关心处理时间,可以像@FirstCall建议的那样在GetHashCode
方法中简单地返回常量值。 如果你关心它,我建议修改你的方法如下:
public int GetHashCode(double d) { return Math.Round(d).GetHashCode(); }
Math.Round
应该在公差= 0.5时正确工作,对于另一个公差值,您应该改进这一点。
我建议你阅读这篇博文,以熟悉IEqualityComparer
和Linq
。
使用较少代码量的最简单方法总是从GetHashCode
返回常量值 – 它适用于任何容差值,但是,正如我所写,它是对大量数据的非常低效的解决方案。
这里的每个人都在讨论你的代码有什么问题,但实际上你可能遇到了更糟糕的问题。
如果你真的想要像你的标题所说的那样使用容差进行分组,而不是像这些答案那样按整数部分假设(并且你的测试数据支持), GroupBy
不支持这种情况。
GroupBy
要求等价关系 – 你的等式比较器必须建立它
-
x == x
表示所有x
- 如果对于所有
x
和y
,x == y
,y == x
- 如果对于所有
x
,y
和z
,x == y
和y == z
,x == z
z
“在彼此的0.5之内”匹配前两个点,但不是第三个。 0接近0.4,0.4接近0.8,但0不接近0.8。 如果输入0,0.4和0.8,您会期望哪些组?
您的问题是您的GetHashCode
实现,它必须为您认为相等的所有内容返回相等的值。 因此,对于应该转到同一组的两个不同的值d1
和d2
,该方法应该返回相同的哈希码。 为此,将给定数字四舍五入到最近的整数,然后计算其哈希码:
public int GetHashCode(double d) { return Convert.ToInt32(d).GetHashCode(); }
在计算完哈希码之后 ,实际完成了Equal
。 在当前情况下, GetHashCode
返回的哈希值是不同的,因此根本不会执行Equals
。