是否可以为匹配多对多的比较器编写哈希码函数?

时间:2012-06-04 17:45:01

标签: c# iequalitycomparer

我可以为以下比较器逻辑编写哈希码函数吗?

如果来自(A,B,C)的至少两个属性匹配,则My的两个实例相等。

Equals部分很简单,但是我对哈希码部分感到困惑,我的一部分认为它可能不可能。

class MyOtherComparer : IEqualityComparer<My>
{
    public bool Equals(My x, My y)
    {
        if (Object.ReferenceEquals(x, y)) 
            return true;      

        if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null)) 
            return false;

        int matches = 0;

        if(x.A == y.A) matches++;

        if(x.B == y.B) matches++;

        if(x.C == y.C) matches++;

        // match on two out of three
        return  (matches > 1)
    }

    // If Equals() returns true for a pair of objects 
    // then GetHashCode() must return the same value for these objects.
    public int GetHashCode(My x)
    {
       // ???
    }
}

更新:除了Reed Copsey的正确答案之外,Ethan Brown清楚地说明了关于模糊比较器一般有用性的一个非常重要的观点 - 请查看他的答案以及完整的答案了解这个问题/答案的基础。

6 个答案:

答案 0 :(得分:5)

是的,这是可能的。最简单的实现是始终返回常量。

public int GetHashCode(My x) 
{ 
   return 0;
}

GetHashCode文档声明:

  

需要实现以确保如果Equals方法对两个对象x和y返回true,则x的GetHashCode方法返回的值必须等于为y返回的值。

但是,您可以完全自由地为两个不相等的对象返回相同的哈希码。

话虽这么说,这可能会导致某些算法执行得非常糟糕,因为你会遇到很多哈希冲突。但是,鉴于您的奇/唯一等式检查的性质,可能需要这样做。


请注意,这在任何情况下都会有问题。根据您的逻辑,可以有三个对象,comparer.Equals(foo, bar)==truecomparer.Equals(foo, baz)==truecomparer.Equals(baz, bar)==false。在使用IEqualityComparer<T>的许多情况下,这可能会出现问题。

答案 1 :(得分:1)

对于两个相同的对象,哈希码必须相同,但对于两个不同的对象,哈希码不必相同。您可以为所有对象返回相同的值以满足IEqualityComparer个使用者的需求,但我不知道在您的情况下从哈希获得任何速度优势。

答案 2 :(得分:1)

  

我可以为以下比较器逻辑编写哈希码函数吗?

是。您始终可以为任何内容编写哈希代码。问题是它的效率如何。无论如何,你总能拥有:

public int GetHashCode()
{
  return 0;
}

它总是工作,但它的效率非常低*。

答案 3 :(得分:1)

假设我们有2个对象A,B。它们中的每一个都具有属性p1,p2和p3。假设A.p1 == B.p1和A.p3 == B.p3,如果散列函数取决于p2,它对于A和B将是不同的,因此它们不相等。如果你想根据p1和p3计算散列函数,有很多例子,散列函数不会返回正确的散列值,许多相等的对象将不相等。我想我们不能有一个变量函数。您可以使用常量,但如果要将其用作字典或散列表中的散列键,则不会获得接近O(1)的复杂度。

答案 4 :(得分:1)

到达非常量哈希函数的核心问题是,您无法确保跨平等的传递性。通常,平等被认为是可传递的。也就是说,A = B和B = C意味着A = C(这进一步意味着A,B和C都将具有相同的哈希码)。但是,根据您对等式的定义,您可以得到A = B,B = C和A!= C.理想情况下,不相等的元素将具有不同的哈希码,因此A和C将具有不同的哈希码;但它们不能,因为它们都等于B,所以它们都必须具有相同的哈希码。

如果您对整个集合有所了解,那么您可以获得非常量哈希函数的唯一方法就是这样。您必须将集合分区为“相等的bin”,其中bin中的每个元素都等于bin中的其他元素(包括bin的可能性)。完成此分区后,您可以使用它来生成非常量算法(假设您有多个bin)来生成哈希码。

关于平等箱的想法是,可能有许多这样的箱子配置。作为选择标准,您可能希望最大化bin的数量(以改善散列表查找性能)。退化案例(如Reed Copsey的正确答案所示)是你把所有东西放在同一个箱子里(但是,正如超级猫在下面的评论中指出的那样,名称“平等箱”然后变得具有误导性)。这并没有违反哈希值的任何约束,但是它会导致算法中的性能不佳,这些算法期望具有产生非退化分区的值。

正如supercat在下面指出的那样,为了满足哈希值的约束,必须满足以下条件:如果两个元素位于两个不同的bin中,则它们必须不相等(但是,同一个bin中的两个元素不必相等)是平等的。

答案 5 :(得分:0)

看到您的真正问题是处理Except扩展方法, 我决定为你推荐一些东西,虽然不是真的答案。

public class EqualityComparer<T> : IEqualityComparer<T>
{
    private readonly Func<T, T, bool> _comparer;
    private readonly Func<T, int> _hashCoder;

    public EqualityComparer(Func<T, T, bool> comparer, Func<T, int> hashCoder = null)
    {
        if (comparer == null)
        {
            throw new ArgumentNullException("comparer");
        }

        this._comparer = comparer;
        this._hashCoder = hashCoder ?? (x => 0);
    }

    public bool Equals(T x, T y)
    {
        return this._comparer(x, y);
    }

    public int GetHashCode(T obj)
    {
        return this._hashCoder(obj);
    }
}

然后你可以这样使用它:

arr1.Except(arr2, new EqualityComparer<dynamic>((x, y) =>
     {
         if (ReferenceEquals(x, y))
             return true;

         if (ReferenceEquals(x, null) ||
             ReferenceEquals(y, null))
             return false;

         var matches = 0;

         if (x.A == y.A) matches++;
         if (x.B == y.B) matches++;
         if (x.C == y.C) matches++;

         return (matches > 1);
     }));