[英]HashSet with complex equality
考虑以下课程
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还是我错过了什么?
这是不可能的。 这是根本问题。 实际上是有可能的,但是这是一个很难解决的问题。
说明
只是反过来考虑,在这种情况下您的对象不相等? 从代码中,我可以看到此表达式等于什么:
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接口以及要解决的任务的相等算法。
我认为找到相等对象的最佳解决方案是Indexing 。 例如,您可以查看如何创建数据库以及它们如何使用位索引。
为什么哈希是如此残酷?
如果可能的话,世界上所有的数据库都可以轻松地对单个哈希表中的所有内容进行哈希处理,并且可以解决所有快速访问问题。 例如,假设您的对象不是具有属性的对象,而是整个对象的状态(例如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。 结论是,不可能创建比常量更好的哈希函数。
在这种情况下,我要说的是,将一个对象定义为唯一的哈希码(即,重写GetHashCode
)不应是用于特定HashSet
的哈希码。
换句话说,你应该考虑你的类的两个实例相等,如果它们的性质都是平等的 (如果没有任何属性的匹配)。 但是,如果IEqualityComparer<X>
特定条件对它们进行分组,请使用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)
。
您可以考虑创建一个匿名类型,然后从中返回哈希码:
public override int GetHashCode()
{
// Check that an existing code hasn't already been returned
return new { A, B, C + D }.GetHashCode();
}
确保创建一些自动化测试,以验证具有相同值的对象返回相同的哈希码。
请记住,散列代码一旦发出,您就必须继续返回该代码,而不是新代码。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.