3

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

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

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

谢谢你的帮助。

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

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

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

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

4

4 回答 4

1

最简单的方法是使用 Tuple<> 来组合字段的哈希码。

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

您也可以自己组合哈希,但您可能会弄错。

于 2012-04-13T19:13:38.007 回答
1

这里的关键是意识到任何任意大小的对象集合都可以通过简单地将其视为一个 IEnumerable 来进行散列,该 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);                               
 }

这里需要注意的是,它取决于可枚举的顺序。如果需要,可以通过简单地使 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>.

于 2012-04-27T17:37:57.353 回答
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以便 if x.Equals(y), then x.GetHashCode() == y.GetHashCode()(反之不必为真)

于 2012-04-13T19:15:49.237 回答
0

在这种情况下,您不能安全地使用标准 has 表(除非您可以提供额外的限制)。

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

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

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

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

于 2012-04-13T20:29:37.470 回答