具有复杂相等性的HashSet

时间:2016-09-21 07:16:27

标签: c# .net

考虑以下课程

public class X
{
    //Unique per set / never null
    public ulong A { get; set; }

    //Unique per set / never null
    public string B { get; set; }

    //Combination of C and D is Unique per set / both never null
    public string C { get; set; }
    public string D { get; set; }

    public override bool Equals(object obj)
    {
        var x = (X)obj;

        if (A == x.A || B==x.B)
            return true;

        if (C+D==x.C+x.D)
            return true;

        return false;            
    }

    public override int GetHashCode()
    {
        return 0;
    }
}

我想不出写一个哈希函数,其中上述属性的注释组合适用,就像在Equals函数中一样,在这种情况下,我最好从GetHashCode返回0或者我错过了什么吗?

3 个答案:

答案 0 :(得分:1)

这是不可能的。这是根本问题。事实上它是可能的,但要解决这个问题非常困难。

<强>解释

反过来考虑一下,在哪种情况下你的对象不相等?从代码我可以通过这个表达式看出它们是平等的:

return A == x.A || B==x.B || (C+D)==(x.C+x.D)

并不是平等的表达:

return A!=x.A && B!=x.B && (C+D)!=(x.C+x.D)

因此,对于等式表达式中的任何特定值,您的哈希值应该相同,对于非等式表达式中的任何特定值,哈希值应该相同值可以变为无穷大

两种表达式唯一真正可行的解决方案是常量值。但是这个解决方案在性能上不是可选的,因为它只会消除GetHashCode覆盖的每个含义。

考虑使用IEqualityComperer接口,并使用相等算法来解决您正在解决的任务。

我认为找到平等对象的最佳解决方案是索引。您可以看到例如如何创建数据库,以及它们如何使用位索引。

为什么哈希是如此残忍?

如果可能的话,世界上所有数据库都可以轻松地将所有内容哈希到单个哈希表中,并且所有快速访问问题都将得到解决。 例如,假设您的对象不是具有属性的对象,而是整个对象状态(例如,32个布尔属性可以表示为整数)。

散列函数根据此状态计算散列,但在您的情况下,您明确地告诉它的空间中的某些状态实际上是相等的:

class X
{
    bool A;
    bool B;
}

你的空间是:

A      B
false  false  -> 0
false  true   -> 1
true   false  -> 2
true   true   -> 3

如果你这样定义相等:

bool Equal(X x) { return x.A == A || x.B == B; }

您基本上定义了这种状态平等:

0 == 0
0 == 1
0 == 2
0 != 3

1 == 0
1 == 1
1 != 2
1 == 3

2 == 0
2 != 1
2 == 2
2 == 3

3 != 0
3 == 1
3 == 2
3 == 3

此集应具有相同的散列:{0,1,2} {0,1,3} {0,2,3} {1,2,3}

所以,你的所有集合应该是哈希的EQUAL。结论是,创建Hash函数不可能比常量值更好。

答案 1 :(得分:1)

在这种情况下,我会说将对象定义为唯一的哈希码(即覆盖GetHashCode)不应该是用于特定HashSet的哈希码。

换句话说,如果属性的全部相等,则应该考虑类的两个实例(如果任何属性匹配则不一样)。但是,如果您想按特定条件对它们进行分组,请使用IEqualityComparer<X>的具体实现。

另外,强烈考虑使该类不可变。

除此之外,我认为唯一能够正常工作的哈希码是不变的。任何试图比这更聪明的东西都会失败:

// if any of the properties match, consider the class equal
public class AnyPropertyEqualityComparer : IEqualityComparer<X>
{
    public bool Equals(X x, X y)
    {
        if (object.ReferenceEquals(x, y))
            return true;

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

        return (x.A == y.A ||
                x.B == y.B ||
                (x.C + x.D) == (y.C + y.D));                
    }

    public int GetHashCode(X x)
    {
        return 42;
    }
}

由于您必须在任何情况下评估所有属性,HashSet在这种情况下无济于事,您也可以使用普通List<T>(在这种情况下插入一个列表“hashset”中的项目将降级为O(n*n)

答案 2 :(得分:-3)

您可以考虑创建一个匿名类型,然后从中返回哈希码:

public override int GetHashCode()
{
    // Check that an existing code hasn't already been returned

    return new { A, B, C + D }.GetHashCode();
}

确保创建一些自动化测试以验证具有相同值的对象是否返回相同的哈希码。

请记住,一旦发出哈希码,您必须继续返回该代码而不是新代码。