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

时间:2016-09-07 04:11:20

标签: c# linq

我对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

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

6 个答案:

答案 0 :(得分:3)

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

.toString()

答案 1 :(得分: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 ......)。然后我们可以轻松地将其分组。

答案 2 :(得分:1)

在这些类型的情况下,调试器是你的朋友。在Equals方法上设置一个断点。您会注意到Equals课程的DoubleEqualityComparer方法未受到影响。

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

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

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

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

答案 3 :(得分:1)

您的GetHashCode方法应为数字返回相同的值,该值应为&#34;等于&#34;。

EqualityComparer分两步工作:

  1. 检查GetHashCode是否包含此哈希码的值 处理完毕后,此值将进入新的单一组

  2. 如果获得了具有此哈希码的值 - 则checkinq结果为 Equals方法。如果确实如此 - 将当前元素添加到现有组,否则将其添加到新组。

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

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

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

    Math.Round应该正确地适用于公差= 0.5,对于另一个公差值,您应该改进它。

    我建议您阅读this blog post以熟悉IEqualityComparerLinq

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

答案 4 :(得分:1)

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

如果您真的希望按照标题所说的容差进行分组,而不是按照这些答案的整数部分进行分组(并且您的测试数据支持),GroupBy不支持此功能。

GroupBy要求equivalence relation - 您的平等比较器必须确定

    所有x == x
  • x
  • 如果x == yy == x适用于所有xy
  • 如果x == yy == zx == z适用于所有xyz

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

答案 5 :(得分:0)

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

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

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