为任意一组键(任意数据类型)获取可散列对象的最有效方法

时间:2012-04-13 19:07:23

标签: c# .net performance dictionary hash

我编写了一个方法,它需要能够获取任意数量的数据字段,将它们以某种方式组合成一个可哈希的对象,然后在字典中哈希这个对象以便以后查找。

到目前为止,我提出的最好的算法是为每个字段使用ToHashCode(),然后使用某种分隔符(例如“|”)将生成的哈希码连接成一个字符串,然后使用它结果字符串作为字典的唯一键。

有谁知道更有效的方法吗?我想也许有一些方法可以获取每个字段的哈希码,并做一些数学运算将它们组合成一个唯一的哈希数字,但这只是猜测。

感谢您的帮助。

编辑: 我想人们可能会对我的意思感到困惑。元组在这种情况下不起作用,因为我需要将任意个字段组合成一个可哈希的对象。字段数仅在运行时已知,而不是在设计时。

关于将所有哈希码数学地组合成新哈希码的另一个解决方案也不起作用,因为我需要一个对象,它可以用作词典中的键。我相信使用哈希码作为词典的关键是非常危险的。

编辑2: 在考虑了这个之后,我认为我的原始解决方案并不是一个好的解决方案。在存在单个字段的限制情况下,我的解决方案已经退化为将字符串版本的哈希码放入字典中。

我认为也许更好的解决方案是创建一个在其构造函数中使用枚举的新类型,并实现GetHashCode()。然后,GetHashCode()函数将循环遍历枚举的每个值,并在哈希代码函数中执行通常类型的累加器逻辑。这样,对象就可以插入到字典,哈希集等中,并且可以按照您的预期运行。

4 个答案:

答案 0 :(得分:1)

最简单的方法是使用元组<>合并你的字段的哈希码。

var dict = new Dictionary<Tuple<int, string>, MyClass>();
dict[Tuple.Create(myObj.Num, myObj.Str)] = myObj;

你也可以自己组合哈希,但是你可能会犯错误。

答案 1 :(得分:1)

这里的关键是要意识到任何任意大小的对象集合都可以通过简单地将其视为IEnumerable来进行哈希处理,其哈希码取决于枚举的内容。

为此,我简单地创建了一个实现IEnumerable的ValueAwareEnumerable类。此类在其唯一的构造函数中使用可枚举。然后它会覆盖GetHashCode()和Equals(),以便它们依赖于可枚举的内容。 GetHashCode方法很简单:

public override int GetHashCode()
{
    unchecked
    {
        int hash = 983;
        foreach (var item in _wrappedEnumerable)
           if(item != null)
              hash = hash * 457 + item.GetHashCode();
        return hash;
    }
}

和等于:

 public override bool Equals(object obj)
 {
     if (ReferenceEquals(null, obj)) return false;
     if (ReferenceEquals(this, obj)) return true;
     if (obj.GetType() != typeof (ValueAwareEnumerable<T>)) return false;
     return Equals((ValueAwareEnumerable<T>) obj);
 }

 public bool Equals(ValueAwareEnumerable<T> other)
 {
     if (ReferenceEquals(null, other)) return false;
     if (ReferenceEquals(this, other)) return true;

     return _wrappedEnumerable.SequenceEqual(other);                               
 }

这里需要注意的是,它取决于可枚举的 order 。如果需要,可以通过简单地使GetHashCode()和Equals()在迭代之前对可枚举进行排序来使其与顺序无关。

要完成它,只需在某处添加扩展方法以获得良好的衡量标准:

public static IEnumerable<T> ToValueAwareEnumerable<T>(this IEnumerable<T> enumerable)
{
   return new ValueAwareEnumerable<T>(enumerable);
}

你可以做以下事情:

var dictionary = new Dictionary<IEnumerable<int>>();
var veryImportantNumbers = new[] { 5, 8, 13, 20, 3, 100, 55, -5, 0 };
dictionary[veryImportantNumbers.ToValueAwareEnumerable()] = "Pastrami";

这适用于任何数据类型,甚至是混合数据类型,如果您将它们视为IEnumerable<Object>

答案 2 :(得分:0)

  

我想也许有一些方法可以获取每个字段的哈希码,并做一些数学运算将它们组合成一个唯一的哈希数字,但这只是猜测。

是的,这正是你应该做的。这是一个常见的实现:

unchecked
{
    int hash = 983;
    hash = hash * 457 + x.GetHashCode();
    hash = hash * 457 + y.GetHashCode();
    hash = hash * 457 + (z != null ? z.GetHashCode() : 0);
    return hash;
}

请注意,您应该将哈希码用作字典键,因为它不是唯一的(冲突通常很少见,但它们并非不可能)。如果您想将对象本身用作键,则还必须覆盖Equals,以便x.Equals(y),然后x.GetHashCode() == y.GetHashCode()(反过来不必为真)

答案 3 :(得分:0)

在这种情况下,您无法安全地使用标准表(除非您可以提供其他限制)。

提供更好的替代方案需要更多信息,但我在下面有一个建议。其他信息可能包括:

  • 使用案例(如何使用查找系统,为什么需要键的字段部分)
  • 是否可以在设计时定义组合的字段(注意:这不是合并的数量或字段数。而是与定义这些字段的位置/时间/方式相关,以便它们可以组合)。
  • 如果字段是在运行时定义的,那么总共有多少字段(所有字段的数量)。
  • 为这个奇怪的密钥存储了哪些数据?
  • 数据写入/读取的频率是多少?

快速解决方案
使用嵌套哈希表。对于此解决方案,您需要对字段进行排序。第一个字段是第一个表的键。这将指向另一个哈希表,其中第二个字段将是键。在最后一个字段之前,每个字段都会发生这种情况。最后一个字段将是您要查找的数据的关键字 要完成这项工作,您需要定义一个自定义对象,该对象具有数据属性和哈希表的属性。

虽然这是一个使用现有.net数据结构的可行解决方案,但效率不高。如需更有效的解决方案,请提供其他信息。