我有一个List<MyRichObject>
,里面有50个实例。每个实例都有1或2个唯一属性,但在某种程度上它们都是唯一的,因为列表中只有一个位置,等等。
我想提出一种独特的方法来“散列”此列表,因此它与所有其他列表都是唯一的。在.NET 4中有一种聪明的方法吗?
目的是为列表创建一种“monniker”,以便将它们转储到队列中,稍后根据它们的独特价值找到它们。
感谢。
答案 0 :(得分:25)
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
应该为c
和e
生成相同的结果 - 而且确实如此。到现在为止还挺好。现在让我们尝试不按顺序的项目:
var f = new []{ "spam", "bar", "foo" };
哦,哦...... GetSequenceHashCode
表示f
等于c
和e
。为什么会这样?首先将c
作为示例分解为实际的哈希码值:
int hashC = "foo".GetHashCode() ^
"bar".GetHashCode() ^
"spam".GetHashCode();
由于这里的确切数字并不重要,为了更清晰的演示,让我们假装三个字符串的哈希码是foo=8
,bar=16
和spam=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" };
虽然a
和b
生成了不同的序列哈希,GetSequenceHashCode
表明a
,c
和d
都是相同的。为什么?
如果您对自己的数字进行异或,则基本上将其取消,即
8 ^ 8 == 0;
// 8 = 00001000
// ^
// 8 = 00001000
// =
// 0 = 00000000
再次使用相同数字的XOR给出原始结果,即
8 ^ 8 ^ 8 == 8;
// 8 = 00001000
// ^
// 8 = 00001000
// ^
// 8 = 00001000
// =
// 8 = 00001000
因此,如果我们再次查看a
和c
,请替换简化的哈希码:
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
,其中每对foo
和spam
都会自行取消。
答案 1 :(得分:2)
哈希是否必须代表列表的内容?换句话说,您将使用哈希来确定潜在的相等性吗?如果没有,那么只需创建一个新的Guid并使用它。
如果标识符确实需要表示列表的内容,那么您可以根据列表的内容生成哈希码(这将是低效的,因为您将无法缓存此值,因为列表的内容可能会更改)或者完全放弃哈希并使用Enumerable.SequenceEquals
来确定相等。
以下是我如何实现获取List<T>
的哈希码的示例。首先,如果您要获取特定对象的哈希码,您应该确保该对象不会更改。如果该对象确实发生了变化,那么您的哈希码就不再有用了。
使用可以“冻结”的列表(意味着在某个点之后没有添加或删除项目)的最佳方法是调用AsReadOnly
。这将为您提供ReadOnlyCollection<T>
。下面的实现取决于ReadOnlyCollection<T>
只是为了安全,所以请记住:
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否则您的列表哈希码可能会在比较时产生大量误报。