【问题标题】:C# SetEquals on HashSet<List<float>> is false when expected trueHashSet<List<float>> 上的 C# SetEquals 在预期为真时为假
【发布时间】:2018-11-11 05:13:35
【问题描述】:

一位 Python 开发人员在这里做一些 C#(.NET 4.6,Visual Studio 2015 Professional)工作。我正在尝试检查两个HashSets 是否相等。

我有两个 HashSet&lt;List&lt;float&gt;&gt; 我正在尝试使用它们进行比较

thisList.SetEquals(otherList);

但是,这会在我的数据上返回 false。使用来自MSDN HashSet's examples 的示例确实可以按预期工作。但是,在示例中他们使用HashSet&lt;int&gt;,而我使用HashSet&lt;List&lt;float&gt;&gt;

由于我找不到将HashSet 内容打印到 Visual Studio 中的即时窗口中的方法(ToString 返回"System.Collections.Generic.HashSet1[System.Collections.Generic.List1[System.Single]]"),我使用Json.NET JsonConvert.SerializeObject(thisList); 将数据转储到.json磁盘上的文件。

两个文件(每个HashSet的内容是:

[[10.0,15.0],[20.0,25.0]][[10.0,15.0],[20.0,25.0]]

调试时在 Visual Studio 中检查 HashSets 如下所示:

-       thisList    Count = 2   System.Collections.Generic.HashSet<System.Collections.Generic.List<float>>
-       [0] Count = 2   System.Collections.Generic.List<float>
        [0] 10  float
        [1] 15  float
+       Raw View        
-       [1] Count = 2   System.Collections.Generic.List<float>
        [0] 20  float
        [1] 25  float
+       Raw View        
+       Raw View        
-       otherList   Count = 2   System.Collections.Generic.HashSet<System.Collections.Generic.List<float>>
-       [0] Count = 2   System.Collections.Generic.List<float>
        [0] 20  float
        [1] 25  float
+       Raw View        
-       [1] Count = 2   System.Collections.Generic.List<float>
        [0] 10  float
        [1] 15  float
+       Raw View        
+       Raw View        

每个HashSet 包含两个列表(顺序不相关,因为它是一个集合)并且每个列表具有相同的值(具有相同的顺序)。他们应该被认为是平等的。

我应该怎么做才能使这些HashSets 被认为与thisList.SetEquals(otherList); 相等?

编辑:

在每个浮点数上打印coord.ToString("G17")

10
15
20
25
20
25
10
15

【问题讨论】:

  • 简而言之,因为列表相等性将基于 ReferenceEquals 并且很可能每个集合中有两个不同的列表对象(即使可能具有相同的值)。那是在进入将浮点数与严格相等进行比较的问题之前。但是你想用列表的 HashSet 来完成什么?这感觉有点像 X-Y 问题
  • 我喜欢周五下午 X/Y 的味道
  • @lc,感谢您的评论。我有一个玩具PointCollection 类,带有XY 属性的点。我正在实现一个Equals 方法来检查两个集合是否相等,这将是当每个类实例具有相同数量的点时,每个点都具有相同的XY 坐标。我应该使用元组而不是 HashSet 中的列表吗?因为在 Python 中,set([(10.0, 15.0), (20.0, 25.0)]) == set([(20.0, 25.0), (10.0, 15.0)])True
  • @LasseVågsætherKarlsen,更新了问题正文。
  • 这实际上是一个 XY 问题 :)

标签: c# .net generics set hashset


【解决方案1】:

因为您在 HashSet 中使用 List,所以它将两个列表作为参考进行比较,而不是考虑列表中的值。

不要使用 List 来表示 X 和 Y,而是使用 Vector2 或 Point 类。这或多或少是结构应该的样子:

public struct Point
{
    public double X {get; }
    public double Y { get; }

    public Point(double x, double y)
    {
        X = x;
        Y = y;
    }

    public bool Equals(Point other)
    {
        return X.Equals(other.X) && Y.Equals(other.Y);
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        return obj is Point && Equals((Point) obj);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            return (X.GetHashCode() * 397) ^ Y.GetHashCode();
        }
    }
}

【讨论】:

  • 谢谢!我已经有Equals (Point other) 重载的Point 类,但是,我需要添加Equals(object obj)GetHashCode。现在一切都按预期工作。介意写几句你为什么需要Equals(object obj)以及397是什么(我猜是散列的随机种子值?)
  • 您实际上并不需要 Equals(Point other) - 当您比较两点时它会更好/更快,因为它不必执行 if 语句/强制转换。使用 GetHashCode,关键是要提供一个尽可能唯一的数字,同时仍确保任何可能相等的数字都具有相同的数字。该代码是由 Resharper(编码工具)自动生成的 - 我认为他们有 397 的某些原因,这可能在 SO 的某个地方进行了解释。
  • FWIW 如果你可以使用System.Drawing.PointF,你可能不需要重新发明轮子
  • @lc。我一开始也这么认为,但是 PointF 并没有覆盖 GetHashCode 并且不是不可变的
【解决方案2】:

您正在尝试检查HashSet&lt;List&lt;float&gt;&gt;HashSet&lt;List&lt;float&gt;&gt; 的另一个对象的相等操作。这里的问题是为什么它返回 false

现在,在我们讨论HashSet&lt;List&lt;float&gt;&gt; 之前,让我们谈谈如果我检查List&lt;float&gt;List&lt;float&gt; 的另一个对象是否相等(使用下面的代码),那么输出会是什么?

    List<float> list = new List<float>() { 10.0f, 15.0f};
    List<float> anotherList = new List<float>() { 10.0f, 15.0f};

    Console.WriteLine(list.Equals(anotherList));

这个的输出将是

从这里开始Equals 比较对象的引用(不相等)。

现在解决您的问题

您应该在初始化 HashSet 时提供EqualityComparer,它应该根据需要检查类型 T。

    HashSet<List<float>> HashSet1 = new HashSet<List<float>>(new FloatListComparer());
    anotherHashSet1.Add(list);

    HashSet<List<float>> anotherHashSet2 = new HashSet<List<float>>();
    anotherHashSet2.Add(anotherList);

    Console.WriteLine(anotherHashSet1.SetEquals(anotherHashSet2));

上面代码的输出是

是的

我在这里写的EqualityComparer如下所示。

public class FloatListComparer : EqualityComparer<List<float>>
{
    public override bool Equals(List<float> list1, List<float> list2)
    {
        return list1.SequenceEqual(list2);
    }

    public override int GetHashCode(List<float> s)
    {
        return base.GetHashCode();
    }
}

现在的问题是为什么SetEquals 不起作用

如果您在here 检查SetEquals 的实现,那么您会发现它调用了基于检查对象引用的T 的默认比较器。通过提供比较器,SetEquals 使用指定的比较器。

查看现场提琴手here

【讨论】:

  • 这是一个不错的选择,但您不应该对列表进行排序,因为列表中的内容是 X、Y 坐标...
  • @MineR - SO 询问了HashSet&lt;List&lt;float&gt;&gt; 的问题,所以我提供了基于此的实现。请参阅他的问题中的行 --- I have two HashSet&lt;List&lt;float&gt;&gt; which I am trying to compare using。另外,比较两个列表有很多方法,我提供了一种方法。
  • 此解决方案将列表视为列表,考虑到整个问题,这并不是真正要求的。他想一直把数据当作集合来对待。从语义上讲,SetEquals 应该处理 [1,1,1] == [1]
  • 是的,SetEquals 方法会忽略重复条目并处理 [1,1,1] == [1]。
猜你喜欢
  • 2020-10-10
  • 1970-01-01
  • 2022-09-28
  • 1970-01-01
  • 2020-02-25
  • 1970-01-01
  • 2022-01-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多