亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

數組作為字典鍵會產生很多沖突

數組作為字典鍵會產生很多沖突

C#
慕田峪7331174 2023-09-24 15:46:07
我需要使用數字(長整型)列表作為字典鍵,以便對它們進行一些分組計算。當直接使用長數組作為鍵時,我遇到了很多沖突。如果我使用 string.Join(",", myLongs) 作為鍵,它會按照我的預期工作,但速度要慢得多(我認為,因為哈希更復雜)。這是一個演示我的問題的示例:Console.WriteLine("Int32");Console.WriteLine(new[] { 1, 2, 3, 0}.GetHashCode());Console.WriteLine(new[] { 1, 2, 3, 0 }.GetHashCode());Console.WriteLine("String");Console.WriteLine(string.Join(",", new[] { 1, 2, 3, 0}).GetHashCode());Console.WriteLine(string.Join(",", new[] { 1, 2, 3, 0 }).GetHashCode());輸出:Int324312407451601393String406954194406954194如您所見,數組返回不同的哈希值。有沒有辦法既能獲得長數組哈希的性能,又能獲得字符串哈希的唯一性?請參閱下面我自己的答案,了解所有建議的性能比較。關于潛在的重復- 該問題有很多有用的信息,但由于這個問題主要是關于尋找高性能替代方案,我認為它仍然提供了一些此處未提及的有用解決方案。
查看完整描述

5 回答

?
一只甜甜圈

TA貢獻1836條經驗 獲得超5個贊

第一個不同實際上是件好事。數組是一種引用類型,幸運的是它們在哈希生成期間(以某種方式)使用引用。我猜想這類似于機器代碼級別使用的指針,或者某些垃圾收集器級別的值。其中一件事您沒有影響,但如果您將相同的實例分配給新的引用變量,則會被復制。

","在第二種情況下,您將獲得由和(new[] { 1, 2, 3, 0 }).ToString();應返回的內容組成的字符串的哈希值。默認值類似于類名,因此當然在兩種情況下它們都是相同的。當然,字符串具有所有這些有趣的特殊規則,例如“像值類型一樣比較”和“字符串駐留”,因此散列應該是相同的。


查看完整回答
反對 回復 2023-09-24
?
慕妹3242003

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);

}


查看完整回答
反對 回復 2023-09-24
?
12345678_0001

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;

? ? }

}

請注意,這仍然可能不如元組那么高效,因為它仍在迭代數組而不是能夠采用更恒定的表達式。


查看完整回答
反對 回復 2023-09-24
?
慕村225694

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


查看完整回答
反對 回復 2023-09-24
?
慕碼人2483693

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 來與其他數組進行比較。


查看完整回答
反對 回復 2023-09-24
  • 5 回答
  • 0 關注
  • 209 瀏覽

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號