【问题标题】:Create Hash Value on a List?在列表上创建哈希值?
【发布时间】:2011-11-08 20:08:21
【问题描述】:

我有一个List<MyRichObject>,其中包含 50 个实例。每个实例都有 1 或 2 个唯一属性,但在某种程度上它们都是唯一的,因为列表中的位置只有一个,等等。

我想想出一种独特的方式来“散列”这个列表,使其在所有其他列表中都是独一无二的。在 .NET 4 中是否有一种聪明的方法可以做到这一点?

目的是为列表创建一种“monniker”,以便将它们转储到队列中,以便以后根据它们的唯一值找到。

谢谢。

【问题讨论】:

  • 基本上你......对列表进行排序......将每个成员转储到一个字节[],加入所有字节[]......用sha2或你喜欢的散列散列大字节[]函数,你就完成了

标签: c# collections .net-4.0 hash queue


【解决方案1】:

TL;DR

public static int GetSequenceHashCode<T>(this IList<T> sequence)
{
    const int seed = 487;
    const int modifier = 31;

    unchecked
    {
        return sequence.Aggregate(seed, (current, item) =>
            (current*modifier) + item.GetHashCode());
    }            
}

何必再纠结另一个答案?

如果列表中有多个具有相同哈希码的项目,accepted answer 可能会给出危险的不准确结果。例如考虑这些输入:

var a = new []{ "foo" };
var b = new []{ "foo", "bar" };
var c = new []{ "foo", "bar", "spam" };
var d = new []{ "seenoevil", "hearnoevil", "speaknoevil" };

这些都产生不同的结果,表明它们都是独特的集合。伟大的!现在让我们尝试复制:

var e = new []{ "foo", "bar", "spam" };

GetSequenceHashCode 应该为 ce 产生相同的结果 - 确实如此。到目前为止,一切都很好。现在让我们尝试乱序的项目:

var f = new []{ "spam", "bar", "foo" };

呃哦...GetSequenceHashCode 表示f 等于ce,但事实并非如此。为什么会这样?首先将其分解为实际的哈希码值,以c 为例:

int hashC = "foo".GetHashCode() ^ 
            "bar".GetHashCode() ^ 
            "spam".GetHashCode();

由于这里的确切数字并不重要,为了更清楚地演示,我们假设三个字符串的哈希码是foo=8bar=16spam=32。所以:

int hashC = 8 ^ 16 ^ 32;

或将其分解为二进制表示:

8 ^ 16 ^ 32 == 56;

//  8 = 00001000
//  ^
// 16 = 00010000
//  ^
// 32 = 00100000
//  =
// 56   00111000

现在您应该明白为什么此实现会忽略列表中的项目顺序,即8^16^32 = 16^8^32 = 32^16^8 等。

其次,存在重复问题。即使您认为以不同的顺序具有相同的内容是可以的(这不是我鼓励的方法),我认为没有人会争辩以下行为是可取的。让我们尝试在每个列表中包含重复项的变体。

var a = new []{ "foo", "bar", "spam" };
var b = new []{ "foo", "bar", "spam", "foo" };
var c = new []{ "foo", "bar", "spam", "foo", "foo" };
var d = new []{ "foo", "bar", "spam", "foo", "foo", "spam", "foo", "spam", "foo" };

虽然ab 生成不同的序列哈希,但GetSequenceHashCode 表明acd 都是相同的。为什么?

如果你将一个数字与它本身进行异或,你基本上将它取消,即

8 ^ 8 == 0;

//  8 = 00001000
//  ^
//  8 = 00001000
//  =
//  0 = 00000000

XOR 通过相同的数字再次为您提供原始结果,即

8 ^ 8 ^ 8 == 8;

//  8 = 00001000
//  ^
//  8 = 00001000
//  ^
//  8 = 00001000
//  =
//  8 = 00001000

因此,如果我们再次查看ac,替换为简化的哈希码:

var a = new []{ 8, 16, 32 };
var c = new []{ 8, 16, 32, 8, 8 };

哈希码计算如下:

int hashA = 8 ^ 16 ^ 32;         // = 56
int hashC = 8 ^ 16 ^ 32 ^ 8 ^ 8; // = 56
                       // ↑   ↑ 
                       // these two cancel each other out

同样对于d,每对foospam 都会自行抵消。

【讨论】:

  • 很好的答案。将我的实现更改为 IEnumerable 的扩展以包含其他集合。我很好奇种子和修饰符值来自哪里,或者只要修饰符不是 0 或 1,它们是否真的是任意的。
  • 31 和 487 都是质数。为什么要使用素数?这已经在其他答案中广泛涵盖,例如stackoverflow.com/questions/1145217/…
【解决方案2】:

哈希值必须代表列表的内容吗?换句话说,您会使用哈希来确定潜在的平等吗?如果不是,那么只需创建一个新的 Guid 并使用它。

如果标识符确实需要表示列表的内容,那么您可以根据列表的内容生成哈希码(这将是低效的,因为您将无法缓存此值,因为列表的内容可能会发生变化)或者完全放弃哈希并使用Enumerable.SequenceEquals 来确定相等性。


下面是我将如何实现获取List&lt;T&gt; 的哈希码的示例。首先,如果您要获取特定对象的哈希码,您真的应该确保该对象不会改变。如果该对象确实发生了变化,那么您的哈希码将不再有用。

处理可以“冻结”的列表(意味着在某个点之后不再添加或删除任何项目)的最佳方法是致电AsReadOnly。这会给你一个ReadOnlyCollection&lt;T&gt;。为了安全起见,下面的实现取决于ReadOnlyCollection&lt;T&gt;,因此请记住这一点:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

class Example
{
    static void Main()
    {
        var seqOne = new List<int> { 1, 2, 3, 4, 5, 6 };
        var seqTwo = new List<int> { 6, 5, 4, 3, 2, 1 };

        var seqOneCode = seqOne.AsReadOnly().GetSequenceHashCode();
        var seqTwoCode = seqTwo.AsReadOnly().GetSequenceHashCode();

        Console.WriteLine(seqOneCode == seqTwoCode);
    }
}

static class Extensions
{
    public static int GetSequenceHashCode<T>(this ReadOnlyCollection<T> sequence)
    {
        return sequence
            .Select(item => item.GetHashCode())
            .Aggregate((total, nextCode) => total ^ nextCode);
    }
}

哦,最后一件事 - 确保您的 MyRichObject 输入 has a good GetHashCode implementation itself 否则您的列表哈希码在比较时可能会产生很多误报。

【讨论】:

  • 谢谢。这不是为了确定相等性,而是为了根据列表的内容生成一个唯一的值。我制作了 500 个这样的列表并将它们放入队列中,我想检查队列并确保队列中的所有内容都是不同的。
  • @Snowy - 我理解,但检查队列中的不同项目是一个平等的问题。您知道一个项目不同的方式是它不等于任何其他项目。这些列表在放入队列后会发生变化吗?
  • 感谢您帮助我思考这个问题。不,列表一旦进入队列就不会改变。我相信我确实想根据每个列表的内容创建一个哈希。
  • 太棒了。我正在做一些与序列化有关的事情并得到一个哈希值。你的东西看起来好多了。谢谢。
猜你喜欢
  • 2021-06-19
  • 2021-06-29
  • 2015-01-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-04
  • 2020-11-01
相关资源
最近更新 更多