5 回答

TA貢獻1836條經驗 獲得超5個贊
第一個不同實際上是件好事。數組是一種引用類型,幸運的是它們在哈希生成期間(以某種方式)使用引用。我猜想這類似于機器代碼級別使用的指針,或者某些垃圾收集器級別的值。其中一件事您沒有影響,但如果您將相同的實例分配給新的引用變量,則會被復制。
","
在第二種情況下,您將獲得由和(new[] { 1, 2, 3, 0 }).ToString();
應返回的內容組成的字符串的哈希值。默認值類似于類名,因此當然在兩種情況下它們都是相同的。當然,字符串具有所有這些有趣的特殊規則,例如“像值類型一樣比較”和“字符串駐留”,因此散列應該是相同的。

TA貢獻1824條經驗 獲得超6個贊
您的字符串正確地返回相同字符串的相同哈希碼,因為string.GetHashCode()是以這種方式實現的。
的實現int[].GetHashCode()對其內存地址進行一些處理以返回哈希碼,因此具有相同內容的數組將返回不同的哈希碼。
這就是為什么具有相同內容的數組返回不同的哈希碼。
您應該考慮為數組編寫一個包裝類來提供正確的哈希碼,而不是直接使用數組作為鍵。
這樣做的主要缺點是計算哈希碼將是一個 O(N) 操作(它必須是 - 否則它不會代表數組中的所有數據)。
幸運的是,您可以緩存哈希代碼,因此它只計算一次。
使用可變數組作為哈希碼的另一個主要問題是,如果在將數組用作哈希容器(例如字典)的鍵后更改數組的內容,則會破壞該容器。
理想情況下,您只會對從未更改的數組使用這種散列。
考慮到所有這些,一個簡單的包裝器將如下所示:
public sealed class IntArrayKey
{
public IntArrayKey(int[] array)
{
Array = array;
_hashCode = hashCode();
}
public int[] Array { get; }
public override int GetHashCode()
{
return _hashCode;
}
int hashCode()
{
int result = 17;
unchecked
{
foreach (var i in Array)
{
result = result * 23 + i;
}
}
return result;
}
readonly int _hashCode;
}
您可以使用它來代替實際的數組,以生成更合理的哈希代碼。
根據下面的評論,這是該類的一個版本:
制作數組的防御性副本,使其無法被修改。
實現相等運算符。
將底層數組公開為只讀列表,因此調用者可以訪問其內容,但無法破壞其哈希碼。
代碼:
public sealed class IntArrayKey: IEquatable<IntArrayKey>
{
public IntArrayKey(IEnumerable<int> sequence)
{
_array = sequence.ToArray();
_hashCode = hashCode();
Array = new ReadOnlyCollection<int>(_array);
}
public bool Equals(IntArrayKey other)
{
if (other is null)
return false;
if (ReferenceEquals(this, other))
return true;
return _hashCode == other._hashCode && equals(other.Array);
}
public override bool Equals(object obj)
{
return ReferenceEquals(this, obj) || obj is IntArrayKey other && Equals(other);
}
public static bool operator == (IntArrayKey left, IntArrayKey right)
{
return Equals(left, right);
}
public static bool operator != (IntArrayKey left, IntArrayKey right)
{
return !Equals(left, right);
}
public IReadOnlyList<int> Array { get; }
public override int GetHashCode()
{
return _hashCode;
}
bool equals(IReadOnlyList<int> other) // other cannot be null.
{
if (_array.Length != other.Count)
return false;
for (int i = 0; i < _array.Length; ++i)
if (_array[i] != other[i])
return false;
return true;
}
int hashCode()
{
int result = 17;
unchecked
{
foreach (var i in _array)
{
result = result * 23 + i;
}
}
return result;
}
readonly int _hashCode;
readonly int[] _array;
}
如果您想使用上面的類而不需要創建數組的防御性副本,則可以將構造函數更改為:
public IntArrayKey(int[] array)
{
_array = array;
_hashCode = hashCode();
Array = new ReadOnlyCollection<int>(_array);
}

TA貢獻1802條經驗 獲得超5個贊
另一種選擇是利用鮮為人知的方法IEqualityComparer
來實現您自己的哈希和相等比較。關于構建良好的哈希值,您需要注意一些注意事項,并且在密鑰中包含可編輯數據通常不是一個好的做法,因為如果密鑰發生變化,它會帶來不穩定,但它肯定比使用字符串連接。
public class ArrayKeyComparer : IEqualityComparer<int[]>
{
? ? public bool Equals(int[] x, int[] y)
? ? {
? ? ? ? return x == null || y == null?
? ? ? ? ? ? ? x == null && y == null?
? ? ? ? ? ? : x.SequenceEqual(y);
? ? }
? ? public int GetHashCode(int[] obj)
? ? {
? ? ? ? var seed = 0;
? ? ? ? if(obj != null)
? ? ? ? ? ? foreach (int i in obj)
? ? ? ? ? ? ? ? seed %= i.GetHashCode();
? ? ? ? return seed;
? ? }
}
請注意,這仍然可能不如元組那么高效,因為它仍在迭代數組而不是能夠采用更恒定的表達式。

TA貢獻1880條經驗 獲得超4個贊
如果您知道正在使用的數組的長度,則可以使用Tuple
.
Console.WriteLine("Tuple");
Console.WriteLine(Tuple.Create(1, 2, 3, 0).GetHashCode());
Console.WriteLine(Tuple.Create(1, 2, 3, 0).GetHashCode());
輸出
Tuple
1248
1248

TA貢獻1860條經驗 獲得超9個贊
建議如下:
int[] 作為鍵(最初的嘗試——根本不起作用,作為基準包含在內)
字符串作為鍵(原始解決方案——有效,但速度慢)
元組作為鍵(由David建議)
ValueTuple 作為鍵(受 Tuple 啟發)
直接 int[] hash 作為 key
IntArrayKey(由Matthew Watson建議)
int[] 作為Skeet 的 IEqualityComparer 的鍵
int[] 作為David 的 IEqualityComparer 的鍵
我生成了一個列表,其中包含一百萬個長度為 7 的 int[] 數組,其中包含 100 000 到 999 999 之間的隨機數(這是我當前用例的近似值)。然后我復制了這些數組的前 100 000 個,以便有 900 000 個唯一數組,以及 100 000 個列兩次(以強制沖突)。
對于每個解決方案,我枚舉了列表,并將鍵添加到字典中,或者如果鍵已經存在則增加值。然后我打印了有多少個鍵的 Value 大于 1**,以及花費了多少時間。
結果如下(從最好到最差排序):
Algorithm? ? ? ? ? ? ? ? Works?? ?Time usage
NonGenericSkeetEquality? YES? ? ? ? ? ? 392 ms
SkeetEquality? ? ? ? ? ? YES? ? ? ? ? ? 422 ms
ValueTuple? ? ? ? ? ? ? ?YES? ? ? ? ? ? 521 ms
QuickIntArrayKey? ? ? ? ?YES? ? ? ? ? ? 747 ms
IntArrayKey? ? ? ? ? ? ? YES? ? ? ? ? ? 972 ms
Tuple? ? ? ? ? ? ? ? ? ? YES? ? ? ? ? 1 609 ms
string? ? ? ? ? ? ? ? ? ?YES? ? ? ? ? 2 291 ms
DavidEquality? ? ? ? ? ? YES? ? ? 1 139 200 ms ***
int[]? ? ? ? ? ? ? ? ? ? NO? ? ? ? ? ? ?336 ms
IntHash? ? ? ? ? ? ? ? ? NO? ? ? ? ? ? ?386 ms
Skeet IEqualityComparer 僅比直接使用 int[] 作為鍵稍慢,其巨大優勢在于它確實有效,所以我將使用它。
** 我知道這不是一個完全萬無一失的解決方案,因為理論上我可以得到預期的碰撞次數,而實際上并不是我預期的碰撞,但是經過多次運行測試,我相當確定我不。
*** 沒有完成,可能是由于糟糕的散列算法和大量的相等性檢查。必須將數組數量減少到 10 000 個,然后將時間使用量乘以 100 來與其他數組進行比較。
- 5 回答
- 0 關注
- 209 瀏覽
添加回答
舉報