【问题标题】:Most efficient way to get a hashable object for an arbitrary set of keys (of arbitrary data type)为任意一组键(任意数据类型)获取可散列对象的最有效方法
【发布时间】:2012-04-26 04:09:23
【问题描述】:

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

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

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

感谢您的帮助。

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

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

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

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

【问题讨论】:

  • 无论您选择什么,都有发生碰撞的风险。对于您的字符串版本,它可能可以忽略不计。
  • 是的,你永远无法完全避免冲突,因为哈希码只有有限数量的不同可能值。
  • 我会投票使用GetHashCode() 的数学组合并将其用作键,并让它能够很好地处理碰撞。例如Dictionary<int, List<object>>,如果列表包含超过 1 个对象,则比较它们以找到正确的对象。
  • @MgSam 请接受答案或提供其他信息/说明。

标签: c# .net performance dictionary hash


【解决方案1】:

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

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

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

【讨论】:

  • 它也链接了平等。这可能是唯一的选择。
  • 最容易维护,内置的元组也很快。
【解决方案2】:

这里的关键是意识到任何任意大小的对象集合都可以通过简单地将其视为 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&lt;Object&gt;

【讨论】:

  • +1,不要忘记检查散列函数中的空值,它不应该抛出异常。类似hash = hash * 457 + (item == null ? 0 : item.GetHashCode());
【解决方案3】:

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

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

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()(反之不一定为真)

【讨论】:

  • 手动组合散列应该比使用 Tuple 快一点,但这是有代价的。除非仔细分析显示,否则我建议不要手动组合哈希。
  • @EldritchConundrum,我想这取决于你想做什么......我不确定我是否正确理解了 OP 的要求。此外,Tuple 在 .NET 3.5 及更早版本中不可用。
  • 是的,这是真的。在元组之前,您必须手动组合散列。因此,最佳答案取决于 .Net 版本和 OP 所指的“高效”:)
  • 这个问题是它给你一个哈希码,而不是一个可哈希的对象。我需要生成的对象能够进入字典。
  • @MgSam,也许你需要一个自定义的 IEqualityComparer?
【解决方案4】:

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

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

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

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

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

【讨论】:

    猜你喜欢
    • 2010-11-19
    • 1970-01-01
    • 1970-01-01
    • 2020-06-06
    • 2022-11-03
    • 2019-04-23
    • 2018-07-08
    • 2016-05-13
    • 2021-01-10
    相关资源
    最近更新 更多