GroupBy of List容忍是行不通的

本文关键字:行不通 of List double GroupBy | 更新日期: 2023-09-27 18:06:03

我有一个关于c#的Groupby的问题。

我做了一个List如下所示:

List<double> testList = new List<double>();
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<double>
{
    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
我不知道是什么问题。

GroupBy of List<double>容忍是行不通的

请告诉我有什么问题和解决办法。

使用简单的数学。地板得到较低范围的数字,使5.8不应被视为6。

List<double> testList = new List<double>();
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();

您的GetHashCode方法应该为数字返回相同的值,应该是"equal"。

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返回常量值-它将适用于任何容差值,但是,正如我所写的,这是相当低效的解决方案在大量的数据

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

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

每个GetHashCode方法在执行时都应该是原子的,并且对于任意两个相等的比较都应该返回相同的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…)。然后我们就可以很容易地把它分组了。

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

如果你真的想像你的标题所说的那样用一个公差分组,而不是像这些答案假设的那样按整数部分分组(你的测试数据支持),GroupBy不支持这一点。

GroupBy要求一个等价关系——你的等价比较器必须建立

  • x == x for all x
  • xyx == yy == x
  • 如果x == yy == z, x == z对于所有x, yz

"彼此相差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();
}

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