使用Linq与自定义IEqualityComparer求交集

14

长话短说:我有两个对象集合。一个包含好的值(我们称之为“好的”),另一个包含默认值(Mr.“默认”)。我想要求Good和Default的联合交集,以及Default本身的交集。换句话说:Intersect(Union(Good,Default),Default)。你可能认为结果是Default,但问题在于我使用了自定义的IEqualityComparer。

我有以下类:

class MyClass
{
    public string MyString1;
    public string MyString2;
    public string MyString3;
}

class MyEqualityComparer : IEqualityComparer<MyClass>
{
    public bool Equals(MyClass item1, MyClass item2)
    {
        if(item1 == null && item2 == null)
            return true;
        else if((item1 != null && item2 == null) ||
                (item1 == null && item2 != null))
            return false;

        return item1.MyString1.Equals(item2.MyString1) &&
               item1.MyString2.Equals(item2.MyString2);
    }

    public int GetHashCode(MyClass item)
    {
        return new { item.MyString1, item.MyString2 }.GetHashCode();
    }
}

这是我收集的Good和Default集合的特点:

Default:一个包含所有想要的{ MyString1,MyString2 }对的大型集合,但是正如你所猜测的那样,MyString3值是默认值。

Good:一个较小的集合,主要包含在Default集合中的项目,但具有一些良好的MyString3值。它还包含一些不在所需集合中的{ MyString1,MyString2 }。

我想做的是:仅使用Default中存在的Good项,但将其他Default项目添加到其中。

以下是我认为是最好的尝试:

HalfWantedResult = Good.Union(Default, new MyEqualityComparer());
WantedResult= HalfWantedResult.Intersect(Good, new MyEqualityComparer());

我以为它应该可以工作,但我得到的结果基本上只有来自默认集合的好 {MyString1,MyString2} 对组成,所以我在所有地方都有默认值。我还尝试了交换最后一个 Intersect 的 Default 和 Good,但是我得到了相同的结果。


3
你的Equals实现真的很糟糕。会出现不应该存在的哈希冲突。为什么不使用相同的投影(new { item.MyString1, item.MyString2 }),但调用Equals呢? - Jon Skeet
我应该研究一下这个,它可能是问题的一部分。Union使用GetHashCode,而Intersects使用Equals。我还没有真正考虑过那部分。感到羞愧 - Tipx
2个回答

21

首先,这是错误的:

public bool Equals(MyClass item1, MyClass item2)
{
    return GetHashCode(item1) == GetHashCode(item2);
}
如果散列码不同,那么相应的两个项肯定是不同的,但如果它们相等,则不能保证相应的两个项是相等的。 因此,下面是正确的Equals实现:
public bool Equals(MyClass item1, MyClass item2)
{
    if(object.ReferenceEquals(item1, item2))
        return true;
    if(item1 == null || item2 == null)
        return false;
    return item1.MyString1.Equals(item2.MyString1) &&
           item1.MyString2.Equals(item2.MyString2);
}

正如Slacks提出的建议(比我先想到),代码如下:

var Default = new List<MyClass>
{
    new MyClass{MyString1="A",MyString2="A",MyString3="-"},
    new MyClass{MyString1="B",MyString2="B",MyString3="-"},
    new MyClass{MyString1="X",MyString2="X",MyString3="-"},
    new MyClass{MyString1="Y",MyString2="Y",MyString3="-"},
    new MyClass{MyString1="Z",MyString2="Z",MyString3="-"},

};
var Good = new List<MyClass>
{
    new MyClass{MyString1="A",MyString2="A",MyString3="+"},
    new MyClass{MyString1="B",MyString2="B",MyString3="+"},
    new MyClass{MyString1="C",MyString2="C",MyString3="+"},
    new MyClass{MyString1="D",MyString2="D",MyString3="+"},
    new MyClass{MyString1="E",MyString2="E",MyString3="+"},
};
var wantedResult = Good.Intersect(Default, new MyEqualityComparer())
                       .Union(Default, new MyEqualityComparer());

// wantedResult:
// A A +
// B B +
// X X -
// Y Y -
// Z Z -

非常感谢您对我的Equals方法提供的帮助,虽然它并没有解决我的问题,但我还是很感激。如果可以的话,我希望能够给您+2分!:-P - Tipx
我感觉好蠢啊...我误解了关于交集方法的工具提示,而且我的基础数据中有一个错误,所以没有匹配发生。由于我认为这篇文章是最有帮助的,它提供了equals的实现和示例,所以我接受了它。再次感谢。 - Tipx
不客气 ;-). 顺便说一下,如果您的比较器被调用了很多次,并且性能真的很重要,那么您应该考虑重写GetHashCode()以避免为每个调用创建一个匿名类型的新实例。请查看此答案以获取快速有效的实现--> https://dev59.com/EnVC5IYBdhLWcg3wihqv#263416 - digEmAll
我会这样做。虽然我的项目性能不会真的成为问题(只进行大约一百万次检查,每天一次),但我追求更好的编程!(其实我不是一个真正的程序员 :-P) - Tipx
1
Equals的实现不够优化。前言非常难读。相反,考虑使用if (object.ReferenceEquals(item1,item2)) return true;作为第一行。这处理了两者都为空的情况,并加快了两者是同一引用的情况。然后,其余的空检查只需使用if(item1==null||item2==null) return false;即可,这更短,更易于阅读,并且正确,因为如果它们都为空,则已经返回true。 - Kevin Cathcart
显示剩余3条评论

10

你需要检查实际的相等性,而不仅仅是哈希码的相等性。

GetHashCode() 不是(也不能)完全无冲突的,这就是为什么首先需要 Equals 方法的原因。

另外,你可以通过编写以下代码更简单地完成此操作:

WantedResult = Good.Concat(Default).Distinct();
Distinct方法将返回重复项对中的第一个项目,因此这将返回所需的结果。编辑:应该是
WantedResult = Good.Intersect(Default, new MyEqualityComparer())
                   .Union(Default, new MyEqualityComparer());

如果我使用Concat和Distinct,我将得到我想要的结果,但是我也会得到一些来自Good集合的项目,这些项目不在所需范围(默认)内。 - Tipx
Union和Intersect操作是可交换的,所以它不会改变结果,但是确实避免了使用中间变量。 - Tipx
哦,等等,Union和Intersect不是可交换的:(ABC u ABD) n AE = A(ABC n AE) u ABD = ABD - digEmAll
哦,抱歉,我只考虑了我的情况:(ABC u ABD) n ABC = ABC 和 ABC u (ABD n ABC) = ABC。但是,是的,你是对的,它们并不相同。在我的情况下,(ABC u ABD) != (ABD u ABC),因为这不是一个真正的联合。 - Tipx

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接