GetHashCode को ओवरराइड करने के लिए सबसे अच्छा एल्गोरिथ्म क्या है?


1448

.NET में, इस GetHashCodeविधि का उपयोग .NET बेस क्लास लाइब्रेरी में बहुत सारी जगहों पर किया जाता है। इसे ठीक से लागू करना विशेष रूप से एक संग्रह में या समानता का निर्धारण करते समय वस्तुओं को जल्दी से खोजना महत्वपूर्ण है।

क्या मेरे मानक GetHashCodeवर्गों के लिए लागू करने के तरीके पर एक मानक एल्गोरिथ्म या सर्वोत्तम अभ्यास है ताकि मैं प्रदर्शन को नीचा न करूँ?


38
इस सवाल और नीचे दिए गए लेख को पढ़ने के बाद, मैं ओवरराइड को लागू कर सकता हूं GetHashCode। मुझे उम्मीद है कि यह दूसरों के लिए मददगार होगा। एरिक लिपर्ट द्वारा लिखित GetHashCode के लिए दिशानिर्देश और नियम
rene

4
"या समानता का निर्धारण करने के लिए": नहीं! समान हैशकोड वाली दो वस्तुएं आवश्यक नहीं हैं।
थॉमस लेवेस्क

1
@ThomasLevesque आप सही हैं, समान हैश कोड वाली दो वस्तुएं आवश्यक रूप से समान नहीं हैं। लेकिन अभी भी GetHashCode()बहुत से कार्यान्वयन में उपयोग किया जाता है Equals()। मेरे उस कथन का यही मतलब था। GetHashCode()अंदर Equals()अक्सर असमानता को निर्धारित करने के लिए एक शॉर्टकट के रूप में उपयोग किया जाता है , क्योंकि यदि दो वस्तुओं का अलग-अलग हैश कोड होता है, तो उन्हें ऐसी वस्तुएं बनानी होती हैं जो समान नहीं होती हैं और बाकी की समानता-जांच निष्पादित नहीं होती है।
bitbonk

3
@bitbonk आमतौर पर, दोनों को GetHashCode()और Equals()दोनों वस्तुओं के सभी क्षेत्रों को देखने की आवश्यकता होती है (यदि हैशकोड समान है या नहीं, इसकी जाँच की जाती है)। इस वजह से, GetHashCode()अंदर Equals()का एक कॉल अक्सर बेमानी है और प्रदर्शन को कम कर सकता है। Equals()शॉर्ट सर्किट में भी सक्षम हो सकता है, जिससे यह बहुत तेज हो जाता है - हालांकि कुछ मामलों में हैशकोड्स को कैश किया जा सकता है, जिससे GetHashCode()चेक तेजी से और इसके लायक हो जाता है। इस प्रश्न को और देखें ।
NotEnoughData

अद्यतन जन 2020: एरिक लिपर्ट
रिक डेविन

जवाबों:


1602

मैं आमतौर पर जोश बलोच के शानदार प्रभावी जावा में दिए गए कार्यान्वयन के साथ कुछ करता हूं । यह तेज़ है और एक बहुत अच्छा हैश बनाता है जिससे टकराव की संभावना नहीं है। दो अलग-अलग प्रमुख संख्याएँ चुनें, जैसे 17 और 23, और करें:

public override int GetHashCode()
{
    unchecked // Overflow is fine, just wrap
    {
        int hash = 17;
        // Suitable nullity checks etc, of course :)
        hash = hash * 23 + field1.GetHashCode();
        hash = hash * 23 + field2.GetHashCode();
        hash = hash * 23 + field3.GetHashCode();
        return hash;
    }
}

जैसा कि टिप्पणियों में कहा गया है, आप एक बड़े प्राइम को इसके बजाय गुणा करने के लिए चुन सकते हैं। जाहिरा तौर पर 486187739 अच्छा है ... और हालांकि सबसे अधिक उदाहरण मैंने छोटी संख्याओं के साथ देखे हैं जो कि अपराधों का उपयोग करते हैं, कम से कम समान एल्गोरिदम हैं जहां गैर-अभाज्य संख्याओं का अक्सर उपयोग किया जाता है। उदाहरण के लिए, बाद में FNV में उल्लेखनीय रूप से , मैंने उन संख्याओं का उपयोग किया है जो स्पष्ट रूप से अच्छी तरह से काम करती हैं - लेकिन प्रारंभिक मूल्य एक प्रमुख नहीं है। (गुणा निरंतर है प्रधानमंत्री यद्यपि। मैं काफी कैसे महत्वपूर्ण है कि पता नहीं है।)

यह XORदो मुख्य कारणों से आईएनजी हैशकोड के सामान्य अभ्यास से बेहतर है । मान लीजिए कि हमारे पास दो प्रकारों के साथ एक प्रकार है int:

XorHash(x, x) == XorHash(y, y) == 0 for all x, y
XorHash(x, y) == XorHash(y, x) for all x, y

वैसे, पहले का एल्गोरिथ्म वर्तमान में गुमनाम प्रकारों के लिए सी # कंपाइलर द्वारा उपयोग किया जाता है।

यह पेज काफी कुछ विकल्प देता है। मुझे लगता है कि अधिकांश मामलों के लिए उपरोक्त "काफी अच्छा" है और इसे याद रखना और सही तरीके से प्राप्त करना अविश्वसनीय रूप से आसान है। FNV विकल्प इसी तरह सरल है, लेकिन अलग स्थिरांक और का उपयोग करता है XORके बजाय ADDएक संयोजन आपरेशन के रूप में। यह नीचे दिए गए कोड जैसा कुछ दिखता है , लेकिन सामान्य FNV एल्गोरिदम व्यक्तिगत बाइट्स पर काम करता है, इसलिए इसके लिए प्रति 32 बाइट हैश मान के बजाय एक बाइट प्रति प्रदर्शन करने के लिए संशोधन की आवश्यकता होगी। FNV को डेटा की परिवर्तनीय लंबाई के लिए भी डिज़ाइन किया गया है, जबकि जिस तरह से हम इसका उपयोग यहां कर रहे हैं, वह हमेशा समान फ़ील्ड मानों के लिए होता है। इस उत्तर पर टिप्पणियाँ यह बताती हैं कि यहाँ कोड वास्तव में उपरोक्त नमूने के रूप में (नमूना मामले में परीक्षण किए गए) काम नहीं करता है।

// Note: Not quite FNV!
public override int GetHashCode()
{
    unchecked // Overflow is fine, just wrap
    {
        int hash = (int) 2166136261;
        // Suitable nullity checks etc, of course :)
        hash = (hash * 16777619) ^ field1.GetHashCode();
        hash = (hash * 16777619) ^ field2.GetHashCode();
        hash = (hash * 16777619) ^ field3.GetHashCode();
        return hash;
    }
}

ध्यान दें कि एक बात का ध्यान रखें कि आदर्श रूप से आपको अपनी समानता-संवेदनशील (और इस प्रकार हैशकोड-संवेदनशील) स्थिति को संग्रह में जोड़ने के बाद बदलने से रोकना चाहिए जो हैश कोड पर निर्भर करता है।

प्रलेखन के अनुसार :

आप अपरिवर्तनीय संदर्भ प्रकारों के लिए GetHashCode को ओवरराइड कर सकते हैं। सामान्य रूप से, परिवर्तनशील संदर्भ प्रकारों के लिए, आपको GetHashCode को केवल तभी ओवरराइड करना चाहिए:

  • आप उन फ़ील्ड से हैश कोड की गणना कर सकते हैं जो परस्पर नहीं हैं; या
  • आप यह सुनिश्चित कर सकते हैं कि उत्परिवर्तित वस्तु का हैश कोड परिवर्तित नहीं होता है, जबकि ऑब्जेक्ट एक संग्रह में निहित होता है जो उसके हैश कोड पर निर्भर करता है।

8
आपके द्वारा उल्लिखित पुस्तक में वर्णित एल्गोरिदम थोड़ा अधिक विस्तृत है, यह स्पष्ट रूप से बताता है कि विभिन्न प्रकार के डेटा फ़ील्ड के लिए क्या करना है। जैसे: केवल GetHashcode को कॉल करने के बजाय टाइप लॉन्ग यूज़ (int) (फ़ील्ड ^ f >>> 32) के फील्ड्स के लिए। क्या Long.GetHashCodes उस तरह से लागू किया गया है?
bitbonk

13
हाँ, Int64.GetHashCode वास्तव में यही करता है। जावा में, जिसे मुक्केबाजी की आवश्यकता होगी, निश्चित रूप से। यह मुझे याद दिलाता है - पुस्तक में एक लिंक जोड़ने का समय ...
जॉन स्कीट

77
23 कोई अच्छा विकल्प नहीं है, क्योंकि (.net 3.5 SP1 के रूप में) Dictionary<TKey,TValue>कुछ वितरणों में अच्छे वितरण मॉडुलो को मानता है। और 23 उनमें से एक है। इसलिए यदि आपके पास GetHashCodeकम्पैशन हैशकोड को प्रभावित करने के लिए क्षमता 23 के साथ केवल अंतिम योगदान है । तो मैं नहीं बल्कि 29 के बजाय 23 का उपयोग करें
CodesInChaos

23
@CodeInChaos: केवल अंतिम योगदान बाल्टी को प्रभावित करता है - इसलिए, यह सबसे खराब हो सकता है, शब्दकोश में सभी 23 प्रविष्टियों को देखना होगा । यह अभी भी प्रत्येक प्रविष्टि के वास्तविक हैश कोड की जांच करने वाला है, जो सस्ता होगा। यदि आपको कोई ऐसा शब्दकोष मिला है जो छोटा है, तो यह ज्यादा मायने नहीं रखता है।
जॉन स्कीट

20
@ वाजदा: मैं आमतौर पर 0 के लिए प्रभावी हैश कोड के रूप में उपयोग करता हूं null- जो कि क्षेत्र की अनदेखी के समान नहीं है।
जॉन स्कीट

431

अनाम प्रकार

Microsoft पहले से ही एक अच्छा जेनेरिक हैशकोड जनरेटर प्रदान करता है: अपनी संपत्ति / क्षेत्र के मूल्यों को एक अनाम प्रकार पर कॉपी करें और इसे हैश करें:

new { PropA, PropB, PropC, PropD }.GetHashCode();

यह किसी भी गुण के लिए काम करेगा। इसमें बॉक्सिंग का इस्तेमाल नहीं किया गया है। यह सिर्फ अनाम प्रकारों के लिए पहले से लागू किए गए एल्गोरिदम का उपयोग करता है।

ValueTuple - C # 7 के लिए अपडेट करें

जैसा कि @cactuaroid टिप्पणियों में उल्लेख करता है, एक मूल्य टपल का उपयोग किया जा सकता है। यह कुछ कीस्ट्रोक्स को बचाता है और अधिक महत्वपूर्ण रूप से स्टैक पर शुद्ध रूप से निष्पादित होता है (कोई कचरा नहीं):

(PropA, PropB, PropC, PropD).GetHashCode();

(नोट: अनाम प्रकारों का उपयोग करने वाली मूल तकनीक ढेर पर एक वस्तु बनाती है, अर्थात कचरा, चूंकि गुमनाम प्रकार को कक्षाओं के रूप में लागू किया जाता है, हालांकि इसे कंपाइलर द्वारा अनुकूलित किया जा सकता है। यह इन विकल्पों को बेंचमार्क करना दिलचस्प होगा, लेकिन टपल का विकल्प बेहतर होना चाहिए।)


85
हां, अनाम GetHashCodeकार्यान्वयन बहुत प्रभावी है (बीटीडब्लू यह जॉन स्कीट के उत्तर में एक जैसा है), लेकिन इस समाधान के साथ एकमात्र समस्या यह है कि आप किसी भी GetHashCodeकॉल पर एक नया उदाहरण उत्पन्न करते हैं । यह विशेष रूप से बड़े
हैशेड

5
@digEmAll अच्छा बिंदु, मैंने एक नई वस्तु बनाने के ओवरहेड के बारे में नहीं सोचा था। जॉन स्कीट का जवाब सबसे कुशल है और वह मुक्केबाजी का उपयोग नहीं करेगा। (@ कुंभा वीबी में अनियंत्रित को हल करने के लिए, बस एक Int64 (लंबी) का उपयोग करें और गणना के बाद इसे काट दें।)
रिक लव

42
बस यह new { PropA, PropB, PropC, PropD }.GetHashCode()भी कह सकते हैं
सितंबर

17
VB.NET को अनाम प्रकार के निर्माण में Key का उपयोग करना चाहिए: New With {Key PropA}.GetHashCode()अन्यथा GetHashCode विभिन्न ऑब्जेक्ट्स के लिए समान 'पहचान' गुणों के साथ एक ही हैशकोड नहीं लौटाएगा।
डेविड ओसबोर्न

4
@ उस मामले में, मैं IEnumerable को सूची मूल्य के रूप में सहेजने के बजाय हर बार यह मानने के बजाय कि हैशकोड की गणना की जाती है। GetHashCode के अंदर हर बार कैक्लुलेटिंग ToList कई स्थितियों में प्रदर्शन को नुकसान पहुंचा सकता है।
रिक लव

105

यहाँ मेरा हैशकोड सहायक है।
यह लाभ है कि यह सामान्य प्रकार के तर्कों का उपयोग करता है और इसलिए मुक्केबाजी का कारण नहीं होगा:

public static class HashHelper
{
    public static int GetHashCode<T1, T2>(T1 arg1, T2 arg2)
    {
         unchecked
         {
             return 31 * arg1.GetHashCode() + arg2.GetHashCode();
         }
    }

    public static int GetHashCode<T1, T2, T3>(T1 arg1, T2 arg2, T3 arg3)
    {
        unchecked
        {
            int hash = arg1.GetHashCode();
            hash = 31 * hash + arg2.GetHashCode();
            return 31 * hash + arg3.GetHashCode();
        }
    }

    public static int GetHashCode<T1, T2, T3, T4>(T1 arg1, T2 arg2, T3 arg3, 
        T4 arg4)
    {
        unchecked
        {
            int hash = arg1.GetHashCode();
            hash = 31 * hash + arg2.GetHashCode();
            hash = 31 * hash + arg3.GetHashCode();
            return 31 * hash + arg4.GetHashCode();
        }
    }

    public static int GetHashCode<T>(T[] list)
    {
        unchecked
        {
            int hash = 0;
            foreach (var item in list)
            {
                hash = 31 * hash + item.GetHashCode();
            }
            return hash;
        }
    }

    public static int GetHashCode<T>(IEnumerable<T> list)
    {
        unchecked
        {
            int hash = 0;
            foreach (var item in list)
            {
                hash = 31 * hash + item.GetHashCode();
            }
            return hash;
        }
    }

    /// <summary>
    /// Gets a hashcode for a collection for that the order of items 
    /// does not matter.
    /// So {1, 2, 3} and {3, 2, 1} will get same hash code.
    /// </summary>
    public static int GetHashCodeForOrderNoMatterCollection<T>(
        IEnumerable<T> list)
    {
        unchecked
        {
            int hash = 0;
            int count = 0;
            foreach (var item in list)
            {
                hash += item.GetHashCode();
                count++;
            }
            return 31 * hash + count.GetHashCode();
        }
    }

    /// <summary>
    /// Alternative way to get a hashcode is to use a fluent 
    /// interface like this:<br />
    /// return 0.CombineHashCode(field1).CombineHashCode(field2).
    ///     CombineHashCode(field3);
    /// </summary>
    public static int CombineHashCode<T>(this int hashCode, T arg)
    {
        unchecked
        {
            return 31 * hashCode + arg.GetHashCode();   
        }
    }

इसके अलावा यह एक धाराप्रवाह इंटरफ़ेस प्रदान करने के लिए विस्तार विधि है, इसलिए आप इसे इस तरह से उपयोग कर सकते हैं:

public override int GetHashCode()
{
    return HashHelper.GetHashCode(Manufacturer, PartN, Quantity);
}

या इस तरह:

public override int GetHashCode()
{
    return 0.CombineHashCode(Manufacturer)
        .CombineHashCode(PartN)
        .CombineHashCode(Quantity);
}

5
T[]अलग से कोई ज़रूरत नहीं है क्योंकि यह पहले से ही हैIEnumerable<T>
nawfal

5
आप उन तरीकों को रिफ्लेक्टर कर सकते हैं और कोर लॉजिक को एक फ़ंक्शन तक सीमित कर सकते हैं
nawfal

12
संयोग से, 31 सीपीयू पर एक पारी और घटाव है, जो बहुत तेजी से होता है।
चुई ते

4
@nightcoder आप इस्तेमाल कर सकते हैं पैरामीटर
13

6
@ChuiTey यह कुछ ऐसा है जो सभी मेर्सेन प्राइम में है।
छप्पर

63

हेल्पर लाइब्रेरी में मेरे पास हैशिंग क्लास है जो मैं इस उद्देश्य के लिए उपयोग करता हूं।

/// <summary> 
/// This is a simple hashing function from Robert Sedgwicks Hashing in C book.
/// Also, some simple optimizations to the algorithm in order to speed up
/// its hashing process have been added. from: www.partow.net
/// </summary>
/// <param name="input">array of objects, parameters combination that you need
/// to get a unique hash code for them</param>
/// <returns>Hash code</returns>
public static int RSHash(params object[] input)
{
    const int b = 378551;
    int a = 63689;
    int hash = 0;

    // If it overflows then just wrap around
    unchecked
    {
        for (int i = 0; i < input.Length; i++)
        {
            if (input[i] != null)
            {
                hash = hash * a + input[i].GetHashCode();
                a = a * b;
            }
        }
    }

    return hash;
}

फिर, बस आप इसे इस रूप में उपयोग कर सकते हैं:

public override int GetHashCode()
{
    return Hashing.RSHash(_field1, _field2, _field3);
}

मैंने इसके प्रदर्शन का आकलन नहीं किया, इसलिए किसी भी प्रतिक्रिया का स्वागत है।


26
यदि फ़ील्ड मूल्य प्रकार हैं, तो ठीक है, यह मुक्केबाजी का कारण होगा।
नाइटकोड

5
"ओवरफ्लो एक्ससेप्शन को पकड़कर बाद में बढ़ाया जा सकता है" का पूरा बिंदु uncheckedअतिप्रवाह पर अपवाद से बचने के लिए है जिस पर वांछित है GetHashCode। तो यह गलत नहीं है अगर मूल्य अधिक हो जाता है intऔर यह बिल्कुल भी चोट नहीं पहुंचाता है।
टिम श्मेल्टर

1
इस एल्गोरिथ्म के साथ एक मुद्दा यह है कि नल से भरा कोई भी सरणी हमेशा 0 पर वापस आ जाएगी, इसकी लंबाई चाहे जो भी हो
नाथन एडम्स

2
यह सहायक विधि एक नई वस्तु भी आवंटित करती है []
जेम्स न्यूटन-किंग

1
जैसा कि @NathanAdams उल्लेख करता है, इस तथ्य nullको पूरी तरह से छोड़ दिया गया है जो आपको अप्रत्याशित परिणाम दे सकता है। उन्हें छोड़ देने के बजाय, आपको अशक्त input[i].GetHashCode()होने के बजाय कुछ निरंतर मूल्य का उपयोग करना चाहिए input[i]
डेविड श्वार्ट्ज

58

यहाँ जॉन हेलेट के कार्यान्वयन का उपयोग करके मेरा सहायक वर्ग है

public static class HashCode
{
    public const int Start = 17;

    public static int Hash<T>(this int hash, T obj)
    {
        var h = EqualityComparer<T>.Default.GetHashCode(obj);
        return unchecked((hash * 31) + h);
    }
}

उपयोग:

public override int GetHashCode()
{
    return HashCode.Start
        .Hash(_field1)
        .Hash(_field2)
        .Hash(_field3);
}

यदि आप System.Int32 के लिए एक्सटेंशन विधि लिखने से बचना चाहते हैं:

public readonly struct HashCode
{
    private readonly int _value;

    public HashCode(int value) => _value = value;

    public static HashCode Start { get; } = new HashCode(17);

    public static implicit operator int(HashCode hash) => hash._value;

    public HashCode Hash<T>(T obj)
    {
        var h = EqualityComparer<T>.Default.GetHashCode(obj);
        return unchecked(new HashCode((_value * 31) + h));
    }

    public override int GetHashCode() => _value;
}

यह अभी भी किसी भी ढेर आवंटन से बचा जाता है और बिल्कुल उसी तरह उपयोग किया जाता है:

public override int GetHashCode()
{
    // This time `HashCode.Start` is not an `Int32`, it's a `HashCode` instance.
    // And the result is implicitly converted to `Int32`.
    return HashCode.Start
        .Hash(_field1)
        .Hash(_field2)     
        .Hash(_field3);
}

संपादित करें (मई 2018): EqualityComparer<T>.Defaultगेटटर अब एक जेआईटी आंतरिक है - इस ब्लॉग पोस्ट में स्टीफन टूब द्वारा पुल अनुरोध का उल्लेख किया गया है ।


1
मैं तृतीयक ऑपरेटर के साथ लाइन को बदलूंगा:var h = Equals(obj, default(T)) ? 0 : obj.GetHashCode();
बिल बैरी

मुझे विश्वास है कि टर्नरी ऑपरेटर के साथ obj != nullएक boxनिर्देश संकलित किया जाएगा जो कि यदि Tमूल्य प्रकार है तो मेमोरी आवंटित करेगा । इसके बजाय आप उपयोग कर सकते हैं obj.Equals(null)जो Equalsविधि के एक आभासी कॉल को संकलित करेगा ।
मार्टिन लेवर्स स्टेज

क्योंकि this.hashCode != h। यह समान मूल्य नहीं लौटाएगा।
फाक गर

क्षमा करें, इसे संपादित करने के बजाय मेरी टिप्पणी को हटाने का प्रबंधन करें। क्या नई संरचना बनाने के लिए यह अधिक फायदेमंद है फिर हैशकोड को गैर-रीडायनली में बदलें और करें: "अनियंत्रित {this.hashCode ^ = h * 397;} इसे वापस करें;" उदाहरण के लिए?
एरिक कार्लसन

अपरिवर्तनीयता के अपने लाभ हैं ( क्यों उत्परिवर्ती संरचनाएं बुराई हैं? )। प्रदर्शन के बारे में, जो मैं करता हूं वह बहुत सस्ता है क्योंकि यह ढेर में कोई स्थान आवंटित नहीं करता है।
फाक गूर

30

.NET मानक 2.1 और ऊपर

यदि आप .NET मानक 2.1 या इसके बाद के संस्करण का उपयोग कर रहे हैं, तो आप System.HashCode संरचना का उपयोग कर सकते हैं । इसका उपयोग करने के दो तरीके हैं:

HashCode.Combine

Combineविधि, एक हैश कोड बनाने के लिए इस्तेमाल किया जा सकता है आठ वस्तुओं के लिए छोड़ दिया।

public override int GetHashCode() => HashCode.Combine(this.object1, this.object2);

HashCode.Add

Addविधि संग्रह से निपटने के लिए मदद करता है:

public override int GetHashCode()
{
    var hashCode = new HashCode();
    hashCode.Add(this.object1);
    foreach (var item in this.collection)
    {
        hashCode.Add(item);
    }
    return hashCode.ToHashCode();
}

GetHashCode मेड आसान

अधिक विवरण और टिप्पणियों के लिए आप पूरी ब्लॉग पोस्ट ' GetHashCode मेड ईज़ी ' पढ़ सकते हैं ।

उपयोग उदाहरण

public class SuperHero
{
    public int Age { get; set; }
    public string Name { get; set; }
    public List<string> Powers { get; set; }

    public override int GetHashCode() =>
        HashCode.Of(this.Name).And(this.Age).AndEach(this.Powers);
}

कार्यान्वयन

public struct HashCode : IEquatable<HashCode>
{
    private const int EmptyCollectionPrimeNumber = 19;
    private readonly int value;

    private HashCode(int value) => this.value = value;

    public static implicit operator int(HashCode hashCode) => hashCode.value;

    public static bool operator ==(HashCode left, HashCode right) => left.Equals(right);

    public static bool operator !=(HashCode left, HashCode right) => !(left == right);

    public static HashCode Of<T>(T item) => new HashCode(GetHashCode(item));

    public static HashCode OfEach<T>(IEnumerable<T> items) =>
        items == null ? new HashCode(0) : new HashCode(GetHashCode(items, 0));

    public HashCode And<T>(T item) => 
        new HashCode(CombineHashCodes(this.value, GetHashCode(item)));

    public HashCode AndEach<T>(IEnumerable<T> items)
    {
        if (items == null)
        {
            return new HashCode(this.value);
        }

        return new HashCode(GetHashCode(items, this.value));
    }

    public bool Equals(HashCode other) => this.value.Equals(other.value);

    public override bool Equals(object obj)
    {
        if (obj is HashCode)
        {
            return this.Equals((HashCode)obj);
        }

        return false;
    }

    public override int GetHashCode() => this.value.GetHashCode();

    private static int CombineHashCodes(int h1, int h2)
    {
        unchecked
        {
            // Code copied from System.Tuple a good way to combine hashes.
            return ((h1 << 5) + h1) ^ h2;
        }
    }

    private static int GetHashCode<T>(T item) => item?.GetHashCode() ?? 0;

    private static int GetHashCode<T>(IEnumerable<T> items, int startHashCode)
    {
        var temp = startHashCode;

        var enumerator = items.GetEnumerator();
        if (enumerator.MoveNext())
        {
            temp = CombineHashCodes(temp, GetHashCode(enumerator.Current));

            while (enumerator.MoveNext())
            {
                temp = CombineHashCodes(temp, GetHashCode(enumerator.Current));
            }
        }
        else
        {
            temp = CombineHashCodes(temp, EmptyCollectionPrimeNumber);
        }

        return temp;
    }
}

क्या एक अच्छा एल्गोरिथ्म बनाता है?

गति

एक हैश कोड की गणना करने वाला एल्गोरिथ्म तेज होना चाहिए। एक साधारण एल्गोरिथ्म आमतौर पर एक तेज़ होने वाला है।

नियतात्मक

हैशिंग एल्गोरिथ्म को नियतात्मक होना चाहिए अर्थात एक ही इनपुट दिया जाना चाहिए जो हमेशा एक ही आउटपुट का उत्पादन करता है।

टकराव कम करें

हैश कोड की गणना करने वाले एल्गोरिथ्म को हैश टक्करों को एक माइनम में रखने की आवश्यकता होती है । हैश टक्कर एक ऐसी स्थिति है जो तब होती है जब GetHashCodeदो अलग-अलग वस्तुओं पर दो कॉल समान हैश कोड का उत्पादन करते हैं। ध्यान दें कि टकरावों की अनुमति है (कुछ को गलतफहमी है कि वे नहीं हैं) लेकिन उन्हें न्यूनतम रखा जाना चाहिए।

एक अच्छा हैश फ़ंक्शन अपेक्षित आउटपुट रेंज पर समान रूप से अपेक्षित इनपुट को मैप करना चाहिए। इसमें एकरूपता होनी चाहिए।

निवारक के DoS

.NET कोर में हर बार जब आप किसी एप्लिकेशन को पुनरारंभ करते हैं तो आपको अलग-अलग हैश कोड मिलेंगे। यह डेनियल ऑफ सर्विस हमलों (DoS) को रोकने के लिए एक सुरक्षा सुविधा है। .NET फ्रेमवर्क के लिए आपको निम्नलिखित App.config फ़ाइल को जोड़कर इस सुविधा को सक्षम करना चाहिए :

<?xml version ="1.0"?>  
<configuration>  
   <runtime>  
      <UseRandomizedStringHashAlgorithm enabled="1" />  
   </runtime>  
</configuration>

इस विशेषता के कारण, हैश कोड का उपयोग कभी भी उन एप्लिकेशन डोमेन के बाहर नहीं किया जाना चाहिए जिसमें वे बनाए गए थे, उन्हें कभी भी एक संग्रह में मुख्य फ़ील्ड के रूप में उपयोग नहीं किया जाना चाहिए और उन्हें कभी भी जारी नहीं रखना चाहिए।

इसके बारे में यहाँ और पढ़ें ।

क्रिप्टोग्राफिक रूप से सुरक्षित?

एल्गोरिथ्म में क्रिप्टोग्राफिक हैश फ़ंक्शन नहीं होना चाहिए । इसका अर्थ यह है कि निम्नलिखित शर्तों को पूरा नहीं करना है:

  • यह एक संदेश है कि एक दिया हैश मूल्य पैदावार के लिए संभव है
  • एक ही हैश मान के साथ दो अलग-अलग संदेश खोजना संभव है
  • एक संदेश में एक छोटा सा परिवर्तन हैश मूल्य को इतने बड़े पैमाने पर बदलना चाहिए कि नया हैश मान पुराने हैश मान (हिमस्खलन प्रभाव) के साथ असंबंधित दिखाई दे।

29

अधिकांश मामलों में जहां समान () कई क्षेत्रों की तुलना करता है, तो यह वास्तव में मायने नहीं रखता है अगर आपका गेटहैश () एक क्षेत्र या कई पर हैश हो। आपको बस यह सुनिश्चित करना होगा कि हैश की गणना करना वास्तव में सस्ता है ( कोई आवंटन नहीं , कृपया) और तेज़ ( कोई भारी गणना और निश्चित रूप से कोई डेटाबेस कनेक्शन नहीं) और एक अच्छा वितरण प्रदान करता है।

भारी उठाने के बराबर () विधि का हिस्सा होना चाहिए; समतुल्य वस्तुओं को यथासंभव समतुल्य () सक्षम करने के लिए हैश बहुत सस्ता ऑपरेशन होना चाहिए।

और एक अंतिम सिरा: GetHashCode () पर कई अप्लायन्स रनों के स्थिर होने पर भरोसा न करें । कई .Net प्रकार पुनः आरंभ करने के बाद अपने हैश कोड की गारंटी नहीं देते हैं, इसलिए आपको मेमोरी डेटा संरचनाओं के लिए केवल GetHashCode () के मूल्य का उपयोग करना चाहिए।


10
"ज्यादातर मामलों में जहां समान () कई क्षेत्रों की तुलना करता है, तो यह वास्तव में मायने नहीं रखता है अगर आपका गेटहैश () एक क्षेत्र या कई क्षेत्रों पर हैश करता है।" यह खतरनाक सलाह है, क्योंकि वस्तुओं के लिए जो केवल संयुक्त राष्ट्र के हैशेड क्षेत्रों में भिन्न होती हैं, आपको हैश टकराव मिलेगा। यदि ऐसा बार-बार होता है, तो हैश-आधारित संग्रह (हैशपॅट, हैशसेट आदि) का प्रदर्शन सबसे खराब स्थिति में (O (n) तक) ख़राब हो जाएगा।
सेल्के

10
यह वास्तव में जावा में हुआ: जेडडीके स्ट्रिंग के शुरुआती संस्करणों में ।शकोड () केवल स्ट्रिंग की शुरुआत माना जाता है; यदि आपने HashMaps में कुंजियों के रूप में स्ट्रिंग्स का उपयोग किया है, तो यह प्रदर्शन समस्याओं का कारण बनता है जो केवल अंत में भिन्न होता है (जो URL के लिए सामान्य है)। इसलिए एल्गोरिथ्म को बदल दिया गया (JDK 1.2 या 1.3 में मेरा मानना ​​है)।
साल्स्के

3
यदि वह एक फ़ील्ड 'एक अच्छा वितरण प्रदान करता है' (मेरे उत्तर का अंतिम भाग), तो एक फ़ील्ड पर्याप्त है .. यदि यह एक अच्छा वितरण प्रदान नहीं करता है , तो (और बस तब) आपको एक और गणना की आवश्यकता है। (जैसे सिर्फ एक अन्य क्षेत्र का उपयोग करें जो एक अच्छा वितरण प्रदान करता है , या कई क्षेत्रों का उपयोग करता है)
बर्ट हुइजेन

मुझे नहीं लगता कि GetHashCodeमेमोरी एलोकेशन करने में कोई समस्या है , बशर्ते कि यह केवल पहली बार इस्तेमाल किया गया हो (बाद में किए गए इनवोकेशन के साथ केवल कैश्ड परिणाम लौटाया जाए)। महत्वपूर्ण बात यह नहीं है कि टकरावों से बचने के लिए व्यक्ति को बड़ी लंबाई में जाना चाहिए, बल्कि यह भी कि "सिस्टमिक" टक्करों से बचना चाहिए। यदि एक प्रकार के दो intफ़ील्ड हैं oldXऔर newXजो अक्सर एक से भिन्न होते हैं, तो oldX^newXहैश मान 1, 2, 4, या 8. के ​​ऐसे रिकॉर्ड हैश मानों का 90% असाइन करेगा oldX+newX[अनियंत्रित अंकगणित का उपयोग करना ] अधिक टकराव उत्पन्न कर सकता है ...
सुपरकैट

1
... अधिक परिष्कृत फ़ंक्शन की तुलना में, लेकिन 1,000,000 चीजों का एक संग्रह जिसमें 500,000 अलग-अलग हैश मूल्य हैं, यदि प्रत्येक हैश मान में दो संबद्ध चीजें हैं, और बहुत बुरी तरह से अगर एक हैश मूल्य में 500,001 चीजें हैं और अन्य में प्रत्येक एक है।
सुपरकट

23

हाल ही में जब तक मेरा जवाब जॉन स्कीट के यहाँ बहुत करीब रहा होगा। हालाँकि, मैंने हाल ही में एक परियोजना शुरू की जिसमें पावर-ऑफ-दो हैश टेबल का उपयोग किया गया, वह हैश टेबल जहाँ आंतरिक तालिका का आकार 8, 16, 32 है, आदि। अभाज्य संख्या संख्याओं के पक्ष में एक अच्छा कारण है, लेकिन वहाँ पावर ऑफ़ टू साइज़ के भी कुछ फ़ायदे हैं।

और यह बहुत ज्यादा चूसा। इसलिए थोड़े से प्रयोग और शोध के बाद मैंने निम्नलिखित के साथ अपने हैश को फिर से शुरू किया:

public static int ReHash(int source)
{
  unchecked
  {
    ulong c = 0xDEADBEEFDEADBEEF + (ulong)source;
    ulong d = 0xE2ADBEEFDEADBEEF ^ c;
    ulong a = d += c = c << 15 | c >> -15;
    ulong b = a += d = d << 52 | d >> -52;
    c ^= b += a = a << 26 | a >> -26;
    d ^= c += b = b << 51 | b >> -51;
    a ^= d += c = c << 28 | c >> -28;
    b ^= a += d = d << 9 | d >> -9;
    c ^= b += a = a << 47 | a >> -47;
    d ^= c += b << 54 | b >> -54;
    a ^= d += c << 32 | c >> 32;
    a += d << 25 | d >> -25;
    return (int)(a >> 1);
  }
}

और फिर मेरी शक्ति की दो हैश तालिका किसी भी अधिक चूसना नहीं था।

इसने मुझे परेशान कर दिया, क्योंकि ऊपर काम नहीं करना चाहिए। या अधिक सटीक रूप से, यह तब तक काम नहीं करना चाहिए जब तक कि मूल GetHashCode()बहुत विशिष्ट तरीके से खराब न हो ।

एक हैशकोड को फिर से मिलाना एक महान हैशकोड में सुधार नहीं कर सकता है, क्योंकि एकमात्र संभावित प्रभाव यह है कि हम कुछ और टकरावों का परिचय देते हैं।

एक हैश कोड को फिर से मिलाना एक भयानक हैश कोड में सुधार नहीं कर सकता है, क्योंकि एकमात्र संभावित प्रभाव हम बदल रहे हैं जैसे मूल्य 53 पर बड़ी संख्या में टकराव 18,3487,291 मूल्य की बड़ी संख्या।

हैश कोड को री-मिक्स करने से केवल एक हैश कोड में सुधार हो सकता है जो कम से कम अपनी रेंज (2 32 संभावित मूल्यों) में पूरी तरह से टकराव से बचने में काफी अच्छी तरह से किया है, लेकिन बुरी तरह से टकराव से बचने के लिए जब एक हैश तालिका में वास्तविक उपयोग के लिए नीचे। जबकि पावर-ऑफ़-टू टेबल के सरल मोडुलो ने इसे और अधिक स्पष्ट कर दिया था, यह अधिक सामान्य प्राइम-नंबर तालिकाओं के साथ नकारात्मक प्रभाव भी डाल रहा था, जो कि स्पष्ट नहीं था (पुनर्वितरण में अतिरिक्त काम लाभ को पछाड़ देगा , लेकिन लाभ अभी भी होगा)।

संपादित करें: मैं भी खुले-संबोधन का उपयोग कर रहा था, जिससे टकराव के प्रति संवेदनशीलता भी बढ़ गई होगी, शायद इस तथ्य से कहीं अधिक यह शक्ति-दो था।

और अच्छी तरह से, यह गड़बड़ी थी कि .NET (या यहां अध्ययन ) string.GetHashCode()में कार्यान्वयन कितना बेहतर हो सकता है (कम टकराव के कारण लगभग 20-30 गुना तेजी से चल रहे परीक्षणों के क्रम पर) और अधिक परेशान करने पर मेरे खुद के हैश कोड सुधार किया जा सकता है (इससे कहीं अधिक)।

सभी GetHashCode () कार्यान्वयन जिन्हें मैंने पिछले समय में कोडित किया था, और वास्तव में इस साइट पर उत्तरों के आधार के रूप में उपयोग किया गया था, वे मेरे मुकाबले बहुत खराब थे । ज्यादातर समय यह बहुत सारे उपयोगों के लिए "काफी अच्छा" था, लेकिन मैं कुछ बेहतर करना चाहता था।

तो मैंने उस प्रोजेक्ट को एक तरफ रख दिया (यह वैसे भी एक पालतू प्रोजेक्ट था) और जल्दी से .NET में एक अच्छा, अच्छी तरह से वितरित हैश कोड का उत्पादन करने के तरीके को देखने लगा।

अंत में मैं SpookyHash को .NET में पोर्ट करने पर बस गया । वास्तव में ऊपर दिया गया कोड 32-बिट इनपुट से 32-बिट आउटपुट का उत्पादन करने के लिए SpookyHash का उपयोग करने का एक तेज़-पथ संस्करण है।

अब, SpookyHash कोड के टुकड़े को याद करने के लिए एक अच्छा त्वरित नहीं है। इसके बारे में मेरा पोर्ट इसलिए भी कम है क्योंकि मैंने इसे बेहतर गति के लिए बहुत कुछ हाथ में लिया है। लेकिन यही कोड पुन: उपयोग के लिए है।

फिर मैंने उस प्रोजेक्ट को एक तरफ रख दिया , क्योंकि जिस तरह से ओरिजिनल प्रोजेक्ट ने बेहतर हैश कोड का उत्पादन करने के सवाल का उत्पादन किया था, इसलिए उस प्रोजेक्ट ने एक बेहतर .NET मेमसीपी का उत्पादन करने का सवाल पैदा किया।

फिर मैं वापस आया, और बहुत सारे ओवरलोड्स का उत्पादन किया जो आसानी से सभी देशी प्रकारों के बारे में (। को छोड़कर decimal) एक हैड कोड में फ़ीड कर सके ।

यह तेज़ है, जिसके लिए बॉब जेनकिंस सबसे अधिक श्रेय के हकदार हैं क्योंकि उनका मूल कोड मैंने अभी भी तेजी से बनाया है, खासकर 64-बिट मशीनों पर जो एल्गोरिथ्म-के लिए अनुकूलित है।

पूरा कोड https://bitbucket.org/JonHanna/spookilysharp/src पर देखा जा सकता है लेकिन विचार करें कि ऊपर दिया गया कोड इसका एक सरलीकृत संस्करण है।

हालाँकि, चूंकि अब यह पहले से ही लिखा गया है, इसलिए इसका उपयोग अधिक आसानी से किया जा सकता है:

public override int GetHashCode()
{
  var hash = new SpookyHash();
  hash.Update(field1);
  hash.Update(field2);
  hash.Update(field3);
  return hash.Final().GetHashCode();
}

यह बीज मान भी लेता है, इसलिए यदि आपको अविश्वासित इनपुट से निपटने की आवश्यकता है और हैश DoS हमलों से बचाव करना चाहते हैं, तो आप अपटाइम या समान के आधार पर एक बीज निर्धारित कर सकते हैं, और हमलावरों द्वारा अप्रत्याशित परिणाम दे सकते हैं:

private static long hashSeed0 = Environment.TickCount;
private static long hashSeed1 = DateTime.Now.Ticks;
public override int GetHashCode()
{
  //produce different hashes ever time this application is restarted
  //but remain consistent in each run, so attackers have a harder time
  //DoSing the hash tables.
  var hash = new SpookyHash(hashSeed0, hashSeed1);
  hash.Update(field1);
  hash.Update(field2);
  hash.Update(field3);
  return hash.Final().GetHashCode();
}

* इसमें एक बड़ा आश्चर्य यह है कि एक रोटेशन विधि हाथ से (x << n) | (x >> -n)सुधारने वाली चीजें बेहतर हुई हैं। मुझे यकीन है कि जिटर ने मेरे लिए इनलाइन किया होगा, लेकिन प्रोफाइलिंग अन्यथा दिखाई गई।

decimalनेट परिप्रेक्ष्य से मूल नहीं है, हालांकि यह C # से है। इसके साथ समस्या यह है कि इसकी अपनी GetHashCode()परिशुद्धता को महत्वपूर्ण मानते हैं जबकि अपने स्वयं के Equals()नहीं। दोनों वैध विकल्प हैं, लेकिन इस तरह मिश्रित नहीं हैं। अपने स्वयं के संस्करण को लागू करने के लिए, आपको एक या दूसरे को चुनने की आवश्यकता है, लेकिन मुझे नहीं पता कि आप क्या चाहते हैं।

Comparison तुलना के माध्यम से। यदि एक तार पर उपयोग किया जाता है, तो 64 बिट्स पर स्पूकीहैश string.GetHashCode()32 बिट्स की तुलना string.GetHashCode()में काफी तेज होता है, जो कि 64 बिट्स की तुलना में थोड़ा तेज होता है , जो कि 32 बिट्स पर स्पूकीहैश की तुलना में काफी तेज है, हालांकि अभी भी काफी तेजी से एक उचित विकल्प है।


जब एकाधिक हैश मानों को एक में मिलाया जाता है, तो मैं longमध्यवर्ती परिणामों के लिए मानों का उपयोग करता हूं , और फिर अंतिम परिणाम को नीचे की ओर मोड़ देता हूं int। क्या यह एक अच्छा विचार है? मेरी चिंता यह है कि कोई हैश = (हैश * 31) + नेक्स्टफिल्ड का उपयोग करता है, तो मिलान मूल्यों के जोड़े केवल हैश के ऊपरी 27 बिट्स को प्रभावित करेंगे। गणना को विस्तार देना longऔर इसमें लपेटने वाले सामान का विस्तार करना उस खतरे को कम करेगा।
सुपरकैट

@ सुपरकार्ट यह आपके अंतिम मुंगिंग के वितरण पर निर्भर करता है। SpookilySharp पुस्तकालय यह सुनिश्चित करेगा कि वितरण अच्छा था, आदर्श रूप से (क्योंकि इसमें ऑब्जेक्ट निर्माण की आवश्यकता नहीं होगी) एक पॉइंटर के लिए एक पॉइंटर पास करके, या इसमें से एक एनुमेरबल्स को पास करने से यह सीधे हैंडल होता है, लेकिन यदि आप पहले से ही ब्लिटेबल नहीं हैं डेटा या एक उपयुक्त गणना, तो .Update()ऊपर दिए गए जवाब के अनुसार कई मानों के साथ कॉल करना होगा।
जॉन हैना

@JonHanna क्या आप अपने द्वारा सामना किए गए समस्याग्रस्त व्यवहार के साथ अधिक सटीक होना चाहते हैं? मैं एक ऐसी लाइब्रेरी को लागू करने की कोशिश कर रहा हूं जो मूल्य वस्तुओं को तुच्छ ( ValueUtils ) लागू करती है और मुझे पावर-ऑफ-दो हैशटेबल्स में खराब हैश गलतफहमी का प्रदर्शन करने वाला एक टेस्टसेट पसंद आएगा।
Eamon Nerbonne

@EamonNerbonne मेरे पास वास्तव में "इस तरह से धीमा समय था" की तुलना में अधिक सटीक कुछ भी नहीं है। जैसा कि मैंने एक संपादन में जोड़ा है, मैं जिस तथ्य का खुले तौर पर उपयोग कर रहा था, वह पावर-ऑफ-टू फैक्टर से अधिक महत्वपूर्ण हो सकता है। मैं एक विशेष परियोजना पर कुछ परीक्षण मामलों को करने की योजना बना रहा हूं, जहां मैं कुछ अलग तरीकों की तुलना करूंगा, इसलिए मेरे पास आपके लिए एक बेहतर उत्तर हो सकता है, हालांकि यह एक उच्च प्राथमिकता नहीं है (एक निजी परियोजना जिसमें कोई दबाव की आवश्यकता नहीं है , इसलिए मैं इसे जब मैं इसे प्राप्त करूँगा ...)
जॉन हैना

@JHHanna: हाँ, मुझे पता है कि व्यक्तिगत परियोजना का कार्यक्रम कैसे चलता है - सौभाग्य! किसी भी मामले में, मुझे लगता है कि मैंने उस अंतिम टिप्पणी को अच्छी तरह से वाक्यांश नहीं दिया था: मेरा मतलब समस्याग्रस्त इनपुट के लिए पूछना था, और जरूरी नहीं कि समस्याओं का विवरण। मुझे वह परीक्षण सेट (या परीक्षण सेट के लिए प्रेरणा) के रूप में उपयोग करना अच्छा लगेगा। किसी भी मामले में - अपने पालतू परियोजना :-) के साथ शुभकामनाएँ।
Eamon Nerbonne

13

यह एक अच्छा है:

/// <summary>
/// Helper class for generating hash codes suitable 
/// for use in hashing algorithms and data structures like a hash table. 
/// </summary>
public static class HashCodeHelper
{
    private static int GetHashCodeInternal(int key1, int key2)
    {
        unchecked
        {
           var num = 0x7e53a269;
           num = (-1521134295 * num) + key1;
           num += (num << 10);
           num ^= (num >> 6);

           num = ((-1521134295 * num) + key2);
           num += (num << 10);
           num ^= (num >> 6);

           return num;
        }
    }

    /// <summary>
    /// Returns a hash code for the specified objects
    /// </summary>
    /// <param name="arr">An array of objects used for generating the 
    /// hash code.</param>
    /// <returns>
    /// A hash code, suitable for use in hashing algorithms and data 
    /// structures like a hash table. 
    /// </returns>
    public static int GetHashCode(params object[] arr)
    {
        int hash = 0;
        foreach (var item in arr)
            hash = GetHashCodeInternal(hash, item.GetHashCode());
        return hash;
    }

    /// <summary>
    /// Returns a hash code for the specified objects
    /// </summary>
    /// <param name="obj1">The first object.</param>
    /// <param name="obj2">The second object.</param>
    /// <param name="obj3">The third object.</param>
    /// <param name="obj4">The fourth object.</param>
    /// <returns>
    /// A hash code, suitable for use in hashing algorithms and
    /// data structures like a hash table.
    /// </returns>
    public static int GetHashCode<T1, T2, T3, T4>(T1 obj1, T2 obj2, T3 obj3,
        T4 obj4)
    {
        return GetHashCode(obj1, GetHashCode(obj2, obj3, obj4));
    }

    /// <summary>
    /// Returns a hash code for the specified objects
    /// </summary>
    /// <param name="obj1">The first object.</param>
    /// <param name="obj2">The second object.</param>
    /// <param name="obj3">The third object.</param>
    /// <returns>
    /// A hash code, suitable for use in hashing algorithms and data 
    /// structures like a hash table. 
    /// </returns>
    public static int GetHashCode<T1, T2, T3>(T1 obj1, T2 obj2, T3 obj3)
    {
        return GetHashCode(obj1, GetHashCode(obj2, obj3));
    }

    /// <summary>
    /// Returns a hash code for the specified objects
    /// </summary>
    /// <param name="obj1">The first object.</param>
    /// <param name="obj2">The second object.</param>
    /// <returns>
    /// A hash code, suitable for use in hashing algorithms and data 
    /// structures like a hash table. 
    /// </returns>
    public static int GetHashCode<T1, T2>(T1 obj1, T2 obj2)
    {
        return GetHashCodeInternal(obj1.GetHashCode(), obj2.GetHashCode());
    }
}

और यहाँ इसका उपयोग कैसे करना है:

private struct Key
{
    private Type _type;
    private string _field;

    public Type Type { get { return _type; } }
    public string Field { get { return _field; } }

    public Key(Type type, string field)
    {
        _type = type;
        _field = field;
    }

    public override int GetHashCode()
    {
        return HashCodeHelper.GetHashCode(_field, _type);
    }

    public override bool Equals(object obj)
    {
        if (!(obj is Key))
            return false;
        var tf = (Key)obj;
        return tf._field.Equals(_field) && tf._type.Equals(_type);
    }
}

1
कुंजी कैसे निर्धारित की जाती हैं? GetHashCode () किसी भी पैरामीटर को नहीं लेता है, इसलिए इसे दो कुंजी के साथ इस एक को कॉल करने की आवश्यकता होती है जिसे किसी भी तरह निर्धारित करने की आवश्यकता होती है। क्षमा करें, आगे की व्याख्या के बिना यह केवल चालाक दिखता है, लेकिन यह अच्छा नहीं है।
माइकल Stum

और आपको जेनेरिक अधिभार की आवश्यकता क्यों है? सभी वस्तुओं की GetHashCode()विधि के बाद से प्रकार महत्वपूर्ण नहीं है (और आपके कोड में उपयोग नहीं किया गया है) , इसलिए आप हमेशा paramsसरणी पैरामीटर के साथ विधि का उपयोग कर सकते हैं । या मुझसे यहां कुछ छूट रहा है?
गेहूँ

4
जब आप जेनेरिक के बजाय ऑब्जेक्ट का उपयोग करेंगे तो आपको बॉक्सिंग और मेमोरी आबंटन मिलेगा, जो आप GetHashCode में नहीं चाहते हैं। तो जेनरिक जाने का रास्ता है।
कोडइन्कॉचो

1
अनुगामी पारी / xor चरण ( h += (h << 10); h ^= (h >> 6); h += (h << 3); h ^= (h >> 11); h += (h << 15);एक कोडस्मेल है: वे किसी भी इनपुट पर निर्भर नहीं होते हैं और मुझे बहुत ही बेमानी लगते हैं।
sehe

1
@ मैग्नस हाँ ठीक है, मैं अपनी मूल टिप्पणी हटा दूंगा। बस थोड़ा सा ध्यान दें कि यह कुछ अन्य समाधानों की तरह तेज़ नहीं हो सकता है, लेकिन जैसा कि आप कहते हैं कि इससे कोई फर्क नहीं पड़ना चाहिए। वितरण बहुत अच्छा है, यहां से अधिकांश समाधानों से बेहतर है, इसलिए मेरी ओर से +1! :)
नवफाल

11

के रूप में https://github.com/dotnet/coreclr/pull/14863 , वहाँ एक नया तरीका हैश कोड है कि सुपर सरल है उत्पन्न करने के लिए है! बस लिखें

public override int GetHashCode()
    => HashCode.Combine(field1, field2, field3);

इससे आपको कार्यान्वयन विवरण के बारे में चिंता किए बिना एक गुणवत्ता हैश कोड उत्पन्न होगा।


यह एक मधुर अलावा की तरह दिखता है ... किसी भी तरह से पता है कि .NET कोर का कौन सा संस्करण जहाज करेगा?
दान जे

1
@DanJ क्या एक सुखद संयोग है, HashCodeआपकी टिप्पणी से कुछ घंटे पहले ही Corefx के बदलावों का विलय कर दिया गया था। प्रकार .NET .NET 2.1 में जहाज करने के लिए स्लेट किया गया है।
जेम्स Ko

यह कमाल है - और काफी उलट समय। Upvoted। :)
दान जे

@DanJ इससे भी बेहतर समाचार-- यह अभी उपलब्ध होना चाहिए, जो CorenetFX के रात के समय डॉटनेट-कोर MyGet फीड पर होस्ट किया गया है।
जेम्स

मीठा - कि मुझे काम पर मदद नहीं करता है, क्योंकि हम काफी नहीं कर रहे हैं कि खून बह रहा बढ़त है, लेकिन अच्छा पता करने के लिए। चीयर्स!
दान जे

9

जॉन स्केट द्वारा ऊपर पोस्ट किए गए एल्गोरिदम का एक और धाराप्रवाह कार्यान्वयन है , लेकिन इसमें कोई आवंटन या मुक्केबाजी संचालन शामिल नहीं है:

public static class Hash
{
    public const int Base = 17;

    public static int HashObject(this int hash, object obj)
    {
        unchecked { return hash * 23 + (obj == null ? 0 : obj.GetHashCode()); }
    }

    public static int HashValue<T>(this int hash, T value)
        where T : struct
    {
        unchecked { return hash * 23 + value.GetHashCode(); }
    }
}

उपयोग:

public class MyType<T>
{
    public string Name { get; set; }

    public string Description { get; set; }

    public int Value { get; set; }

    public IEnumerable<T> Children { get; set; }

    public override int GetHashCode()
    {
        return Hash.Base
            .HashObject(this.Name)
            .HashObject(this.Description)
            .HashValue(this.Value)
            .HashObject(this.Children);
    }
}

कंपाइलर यह सुनिश्चित करेगा कि HashValueजेनेरिक प्रकार की बाधा के कारण वर्ग के साथ नहीं बुलाया जाए। लेकिन HashObjectसामान्य तर्क जोड़ने के लिए कोई संकलक समर्थन नहीं है, एक बॉक्सिंग ऑपरेशन भी जोड़ता है।


8

यहाँ मेरा सरल दृष्टिकोण है। मैं इसके लिए क्लासिक बिल्डर पैटर्न का उपयोग कर रहा हूं। यह टाइपसेफ़ (कोई बॉक्सिंग / अनबॉक्सिंग) नहीं है और .NET 2.0 (कोई एक्सटेंशन विधि आदि) के साथ कंपैटिबल भी नहीं है।

इसका उपयोग इस तरह किया जाता है:

public override int GetHashCode()
{
    HashBuilder b = new HashBuilder();
    b.AddItems(this.member1, this.member2, this.member3);
    return b.Result;
} 

और यहाँ acutal बिल्डर वर्ग है:

internal class HashBuilder
{
    private const int Prime1 = 17;
    private const int Prime2 = 23;
    private int result = Prime1;

    public HashBuilder()
    {
    }

    public HashBuilder(int startHash)
    {
        this.result = startHash;
    }

    public int Result
    {
        get
        {
            return this.result;
        }
    }

    public void AddItem<T>(T item)
    {
        unchecked
        {
            this.result = this.result * Prime2 + item.GetHashCode();
        }
    }

    public void AddItems<T1, T2>(T1 item1, T2 item2)
    {
        this.AddItem(item1);
        this.AddItem(item2);
    }

    public void AddItems<T1, T2, T3>(T1 item1, T2 item2, T3 item3)
    {
        this.AddItem(item1);
        this.AddItem(item2);
        this.AddItem(item3);
    }

    public void AddItems<T1, T2, T3, T4>(T1 item1, T2 item2, T3 item3, 
        T4 item4)
    {
        this.AddItem(item1);
        this.AddItem(item2);
        this.AddItem(item3);
        this.AddItem(item4);
    }

    public void AddItems<T1, T2, T3, T4, T5>(T1 item1, T2 item2, T3 item3, 
        T4 item4, T5 item5)
    {
        this.AddItem(item1);
        this.AddItem(item2);
        this.AddItem(item3);
        this.AddItem(item4);
        this.AddItem(item5);
    }        

    public void AddItems<T>(params T[] items)
    {
        foreach (T item in items)
        {
            this.AddItem(item);
        }
    }
}

आप मंगेश के उत्तर में गेटशकोड फ़ंक्शन के अंदर ऑब्जेक्ट निर्माण से बच सकते हैं। बस लानत स्थिर हैश फ़ंक्शन (जो स्टार्टर हैश की परवाह करता है) को कॉल करें। इसके अलावा, आप AddItems<T>(params T[] items)सहायक कक्षा में अधिक बार विधि का उपयोग कर सकते हैं ( AddItem(T)प्रत्येक बार कॉल करने की तुलना में )।
नवाफ़ल

और this.result * Prime2 * item.GetHashCode()जब आप अक्सर इस्तेमाल करते हैं तो आपको क्या फायदा होता है this.result * Prime2 + item.GetHashCode()?
नवफाल

मैं AddItems<T>(params T[] items)अधिक बार उपयोग नहीं कर सकता क्योंकि typeof(T1) != typeof(T2)आदि
बिटबैंक

ओह हां मैंने याद किया।
नवफाल

5

ReSharper उपयोगकर्ता GetHashCode, Equals, और अन्य के साथ उत्पन्न कर सकते हैं ReSharper -> Edit -> Generate Code -> Equality Members

// ReSharper's GetHashCode looks like this
public override int GetHashCode() {
    unchecked {
        int hashCode = Id;
        hashCode = (hashCode * 397) ^ IntMember;
        hashCode = (hashCode * 397) ^ OtherIntMember;
        hashCode = (hashCode * 397) ^ (RefMember != null ? RefMember.GetHashCode() : 0);
        // ...
        return hashCode;
    }
}

4

यदि हमारे पास 8 से अधिक गुण नहीं हैं (उम्मीद है), तो यहां एक और विकल्प है।

ValueTuple एक संरचना है और एक ठोस प्रतीत होता है GetHashCode कार्यान्वयन है।

इसका मतलब है कि हम बस यह कर सकते हैं:

// Yay, no allocations and no custom implementations!
public override int GetHashCode() => (this.PropA, this.PropB).GetHashCode();

चलो के लिए .NET कोर के वर्तमान कार्यान्वयन पर एक नज़र ValueTupleहै GetHashCode

यह इस प्रकार है ValueTuple:

    internal static int CombineHashCodes(int h1, int h2)
    {
        return HashHelpers.Combine(HashHelpers.Combine(HashHelpers.RandomSeed, h1), h2);
    }

    internal static int CombineHashCodes(int h1, int h2, int h3)
    {
        return HashHelpers.Combine(CombineHashCodes(h1, h2), h3);
    }

और यह है HashHelper:

    public static readonly int RandomSeed = Guid.NewGuid().GetHashCode();

    public static int Combine(int h1, int h2)
    {
        unchecked
        {
            // RyuJIT optimizes this to use the ROL instruction
            // Related GitHub pull request: dotnet/coreclr#1830
            uint rol5 = ((uint)h1 << 5) | ((uint)h1 >> 27);
            return ((int)rol5 + h1) ^ h2;
        }
    }

अंग्रेजी में:

  • लेफ्ट रोटेट (सर्कुलर शिफ्ट) h1 को 5 पदों पर।
  • परिणाम जोड़ें और एक साथ h1।
  • X2 के साथ परिणाम XOR।
  • उपरोक्त ऑपरेशन को {स्थैतिक यादृच्छिक बीज, h1} पर शुरू करें।
  • प्रत्येक आगे के आइटम के लिए, पिछले परिणाम और अगले आइटम (जैसे h2) पर ऑपरेशन करें।

इस ROL-5 हैश कोड एल्गोरिदम के गुणों के बारे में अधिक जानना अच्छा होगा।

अफसोस, ValueTupleहमारे खुद के लिए भ्रामक GetHashCodeउपवास उतना नहीं हो सकता है जितना हम चाहते हैं और उम्मीद करते हैं। संबंधित चर्चा में यह टिप्पणी दर्शाती है कि सीधे कॉलिंग HashHelpers.Combineअधिक प्रदर्शनकारी है। दूसरी तरफ, वह एक आंतरिक है, इसलिए हमें उस कोड की नकल करनी होगी, जो हमने यहां हासिल किया था। इसके अलावा, हम पहले Combineयादृच्छिक बीज के साथ याद करने के लिए जिम्मेदार होंगे । मुझे नहीं पता कि अगर हम उस कदम को छोड़ते हैं तो परिणाम क्या होते हैं।


मान लिया h1 >> 27जाता है कि इसे अनदेखा करना h1 << 5बराबर है , h1 * 32इसलिए यह समान है h1 * 33 ^ h2इस पृष्ठ के अनुसार , इसे "संशोधित बर्नस्टीन" कहा जाता है।
cactuaroid

3

मेरा अधिकांश कार्य डेटाबेस कनेक्टिविटी के साथ किया जाता है जिसका अर्थ है कि मेरी कक्षाओं में सभी डेटाबेस से एक विशिष्ट पहचानकर्ता है। मैं हमेशा हैशकोड जनरेट करने के लिए डेटाबेस से आईडी का उपयोग करता हूं।

// Unique ID from database
private int _id;

...    
{
  return _id.GetHashCode();
}

इसका मतलब है कि यदि आपके पास व्यक्ति और खाता हैं और वे दोनों के पास आईडी = 1 है, तो उनके पास समान हैश कोड होगा। और यह ठीक नहीं है।
पेरो

15
दरअसल उपरोक्त टिप्पणी गलत है। हमेशा हैश-कोड टकराव की संभावना होगी (एक हैश कोड केवल बाल्टी का पता लगाता है, व्यक्तिगत वस्तु का नहीं)। इसलिए इस तरह के कार्यान्वयन - मिश्रित वस्तुओं वाले हैशकोड के लिए - बहुत सारे टकराव पैदा करेगा, जो अवांछनीय है, लेकिन यह बिल्कुल ठीक होगा यदि आपके पास कभी भी अपने हैशटैब में एक ही प्रकार की वस्तुएं थीं। इसके अलावा यह समान रूप से वितरित नहीं करता है, हालांकि न तो
सिस्टम

2
हैश कोड सिर्फ आईडी हो सकता है, क्योंकि आईडी एक पूर्णांक है। GetHashCode को पूर्णांक पर कॉल करने की कोई आवश्यकता नहीं है (यह एक पहचान समारोह है)
डारेल ली

2
@DarrelLee लेकिन tomo उसका _id एक मार्गदर्शक हो सकता है। यह एक अच्छा कोडिंग अभ्यास है _id.GetHashCodeजैसा कि इरादा स्पष्ट है।
नवफाल

2
@ 1224 उपयोग के पैटर्न के आधार पर यह आपके द्वारा दिए गए कारण के लिए भयानक हो सकता है, लेकिन यह बहुत अच्छा भी हो सकता है; यदि आपके पास बिना छेद वाले ऐसी संख्याओं का अनुक्रम है, तो आप एक आदर्श हैश का उपयोग कर सकते हैं, जो किसी भी एल्गोरिथ्म से बेहतर हो सकता है। यदि आप जानते हैं कि यह मामला है तो आप इस पर भरोसा कर सकते हैं और समानता की जांच छोड़ सकते हैं।
जॉन हैना

3

अगर आप चाहते हैं तो प्राइम को उठाना आसान है, सिवाय नाइटकोडर के समाधान के बहुत समान।

पुनश्च: यह उन समयों में से एक है जहां आप अपने मुंह में थोड़ी सी आहट करते हैं, यह जानते हुए कि यह 9 डिफ़ॉल्ट के साथ एक विधि में फिर से बनाया जा सकता है लेकिन यह धीमा होगा, इसलिए आप बस अपनी आँखें बंद करें और इसके बारे में भूलने की कोशिश करें।

/// <summary>
/// Try not to look at the source code. It works. Just rely on it.
/// </summary>
public static class HashHelper
{
    private const int PrimeOne = 17;
    private const int PrimeTwo = 23;

    public static int GetHashCode<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();
            hash = hash * PrimeTwo + arg6.GetHashCode();
            hash = hash * PrimeTwo + arg7.GetHashCode();
            hash = hash * PrimeTwo + arg8.GetHashCode();
            hash = hash * PrimeTwo + arg9.GetHashCode();
            hash = hash * PrimeTwo + arg10.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3, T4, T5, T6, T7, T8, T9>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();
            hash = hash * PrimeTwo + arg6.GetHashCode();
            hash = hash * PrimeTwo + arg7.GetHashCode();
            hash = hash * PrimeTwo + arg8.GetHashCode();
            hash = hash * PrimeTwo + arg9.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3, T4, T5, T6, T7, T8>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();
            hash = hash * PrimeTwo + arg6.GetHashCode();
            hash = hash * PrimeTwo + arg7.GetHashCode();
            hash = hash * PrimeTwo + arg8.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3, T4, T5, T6, T7>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();
            hash = hash * PrimeTwo + arg6.GetHashCode();
            hash = hash * PrimeTwo + arg7.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3, T4, T5, T6>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();
            hash = hash * PrimeTwo + arg6.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3, T4, T5>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3, T4>(T1 arg1, T2 arg2, T3 arg3, T4 arg4)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3>(T1 arg1, T2 arg2, T3 arg3)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2>(T1 arg1, T2 arg2)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();

            return hash;
        }
    }
}

2
नल नहीं संभालता है।
JJS

1

मैं ऊपर दिए गए उत्तर के रूप में चयनित कार्यान्वयन का उपयोग करके फ़्लोट्स और दशमलव के साथ एक समस्या में भाग गया।

यह परीक्षण विफल हो जाता है (तैरता है; हालांकि मैं नकारात्मक होने के लिए 2 मानों को बदल देता हूं) वही है:

        var obj1 = new { A = 100m, B = 100m, C = 100m, D = 100m};
        var obj2 = new { A = 100m, B = 100m, C = -100m, D = -100m};
        var hash1 = ComputeHash(obj1.A, obj1.B, obj1.C, obj1.D);
        var hash2 = ComputeHash(obj2.A, obj2.B, obj2.C, obj2.D);
        Assert.IsFalse(hash1 == hash2, string.Format("Hashcode values should be different   hash1:{0}  hash2:{1}",hash1,hash2));

लेकिन यह परीक्षा पास होती है (ints के साथ):

        var obj1 = new { A = 100m, B = 100m, C = 100, D = 100};
        var obj2 = new { A = 100m, B = 100m, C = -100, D = -100};
        var hash1 = ComputeHash(obj1.A, obj1.B, obj1.C, obj1.D);
        var hash2 = ComputeHash(obj2.A, obj2.B, obj2.C, obj2.D);
        Assert.IsFalse(hash1 == hash2, string.Format("Hashcode values should be different   hash1:{0}  hash2:{1}",hash1,hash2));

मैंने अपने कार्यान्वयन को आदिम प्रकारों के लिए GetHashCode का उपयोग नहीं करने के लिए बदल दिया और यह बेहतर काम करने लगता है

    private static int InternalComputeHash(params object[] obj)
    {
        unchecked
        {
            var result = (int)SEED_VALUE_PRIME;
            for (uint i = 0; i < obj.Length; i++)
            {
                var currval = result;
                var nextval = DetermineNextValue(obj[i]);
                result = (result * MULTIPLIER_VALUE_PRIME) + nextval;

            }
            return result;
        }
    }



    private static int DetermineNextValue(object value)
    {
        unchecked
        {

                int hashCode;
                if (value is short
                    || value is int
                    || value is byte
                    || value is sbyte
                    || value is uint
                    || value is ushort
                    || value is ulong
                    || value is long
                    || value is float
                    || value is double
                    || value is decimal)
                {
                    return Convert.ToInt32(value);
                }
                else
                {
                    return value != null ? value.GetHashCode() : 0;
                }
        }
    }

1
मामले में आप अन्यथा इरादा uncheckedको प्रभावित नहीं करता है Convert.ToInt32: uint, long, float, doubleऔर decimalयहाँ सब अतिप्रवाह कर सकते हैं।
मार्क हर्ड

1

Microsoft हैशिंग के कई तरीकों के लिए नेतृत्व ...

//for classes that contain a single int value
return this.value;

//for classes that contain multiple int value
return x ^ y;

//for classes that contain single number bigger than int    
return ((int)value ^ (int)(value >> 32)); 

//for classes that contain class instance fields which inherit from object
return obj1.GetHashCode();

//for classes that contain multiple class instance fields which inherit from object
return obj1.GetHashCode() ^ obj2.GetHashCode() ^ obj3.GetHashCode(); 

मैं अनुमान लगा सकता हूं कि कई बड़े इंट के लिए आप इसका उपयोग कर सकते हैं:

int a=((int)value1 ^ (int)(value1 >> 32));
int b=((int)value2 ^ (int)(value2 >> 32));
int c=((int)value3 ^ (int)(value3 >> 32));
return a ^ b ^ c;

और बहु-प्रकार के लिए समान: सभी का intउपयोग करने के लिए पहले परिवर्तित GetHashCode() किया गया है और फिर इंट्यू वैल्यू xor'ed होगा और परिणाम आपका हैश है।

जो लोग हैश को आईडी के रूप में उपयोग करते हैं (मेरा मतलब एक अद्वितीय मूल्य है), हैश स्वाभाविक रूप से कई अंकों तक सीमित है, मुझे लगता है कि यह हैशिंग एल्गोरिथ्म के लिए कम से कम एमडी 5 था।

आप कई मानों को हैशेड मान में बदल सकते हैं और उनमें से कुछ समान हैं, इसलिए इसे पहचानकर्ता के रूप में उपयोग न करें। (शायद किसी दिन मैं आपके घटक का उपयोग करने जा रहा हूं)


7
हैशकोड बनाने के लिए पूर्णांक एक्स-रे एक अच्छी तरह से ज्ञात एंटीपैटर्न है जो वास्तविक दुनिया के मूल्यों के साथ विशेष रूप से उच्च संख्या में टकराव का परिणाम देता है।
जॉन हैना

यहां हर एक पूर्णांक का उपयोग करता है, और हैश के लिए किसी भी प्रकार की गारंटी कभी नहीं होती है, यह सिर्फ उतना ही भिन्न होने की कोशिश करता है जितना कि कुछ टकराव होते हैं।
डेडमैनएन

हां, लेकिन आपका दूसरा और पांचवां टकराव टालने की कोशिश नहीं करता है।
जॉन हैना

1
हां, वह एंटीपार्टर्न काफी सामान्य है।
जॉन हैना

2
पहुँचने के लिए एक संतुलन है। Spookyhash की तरह एक बहुत अच्छा हैश कोड का उपयोग करें और आप बहुत, बहुत बेहतर टक्कर परिहार मिल जाएगा, लेकिन यह इनमें से किसी की तुलना में बहुत अधिक गणना समय होगा (लेकिन जब यह बहुत बड़ी मात्रा में डेटा हैशिंग के लिए आता है, तो Spookyhash बेहद त्वरित है)। टकराव से पहले मूल्यों में से एक पर एक सरल बदलाव टकराव में एक अच्छी कमी के लिए केवल सीमांत अतिरिक्त लागत है। प्राइम-नंबर गुणा समय और गुणवत्ता दोनों फिर से बढ़ रहा है। इसलिए शिफ्ट या मल्टी के बीच बेहतर है इसलिए डिबेटेबल है। सादा Xor हालांकि बहुत बार वास्तविक डेटा पर बहुत अधिक टकराव होता है और सबसे अच्छा बचा जाता है
जॉन हैना

1

यह एक स्थिर सहायक वर्ग है जो जोश बलोच के कार्यान्वयन को लागू करता है; और बॉक्सिंग को "रोकने" के लिए स्पष्ट अधिभार प्रदान करता है, और विशेष रूप से लंबे आदिम के लिए हैश को लागू करने के लिए भी।

आप एक स्ट्रिंग तुलना पास कर सकते हैं जो आपके समान कार्यान्वयन से मेल खाती है।

क्योंकि हैश आउटपुट हमेशा एक इंट होता है, आप बस हैश कॉल को चेन कर सकते हैं।

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;


namespace Sc.Util.System
{
    /// <summary>
    /// Static methods that allow easy implementation of hashCode. Example usage:
    /// <code>
    /// public override int GetHashCode()
    ///     => HashCodeHelper.Seed
    ///         .Hash(primitiveField)
    ///         .Hsh(objectField)
    ///         .Hash(iEnumerableField);
    /// </code>
    /// </summary>
    public static class HashCodeHelper
    {
        /// <summary>
        /// An initial value for a hashCode, to which is added contributions from fields.
        /// Using a non-zero value decreases collisions of hashCode values.
        /// </summary>
        public const int Seed = 23;

        private const int oddPrimeNumber = 37;


        /// <summary>
        /// Rotates the seed against a prime number.
        /// </summary>
        /// <param name="aSeed">The hash's first term.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static int rotateFirstTerm(int aSeed)
        {
            unchecked {
                return HashCodeHelper.oddPrimeNumber * aSeed;
            }
        }


        /// <summary>
        /// Contributes a boolean to the developing HashCode seed.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aBoolean">The value to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, bool aBoolean)
        {
            unchecked {
                return HashCodeHelper.rotateFirstTerm(aSeed)
                        + (aBoolean
                                ? 1
                                : 0);
            }
        }

        /// <summary>
        /// Contributes a char to the developing HashCode seed.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aChar">The value to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, char aChar)
        {
            unchecked {
                return HashCodeHelper.rotateFirstTerm(aSeed)
                        + aChar;
            }
        }

        /// <summary>
        /// Contributes an int to the developing HashCode seed.
        /// Note that byte and short are handled by this method, through implicit conversion.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aInt">The value to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, int aInt)
        {
            unchecked {
                return HashCodeHelper.rotateFirstTerm(aSeed)
                        + aInt;
            }
        }

        /// <summary>
        /// Contributes a long to the developing HashCode seed.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aLong">The value to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, long aLong)
        {
            unchecked {
                return HashCodeHelper.rotateFirstTerm(aSeed)
                        + (int)(aLong ^ (aLong >> 32));
            }
        }

        /// <summary>
        /// Contributes a float to the developing HashCode seed.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aFloat">The value to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, float aFloat)
        {
            unchecked {
                return HashCodeHelper.rotateFirstTerm(aSeed)
                        + Convert.ToInt32(aFloat);
            }
        }

        /// <summary>
        /// Contributes a double to the developing HashCode seed.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aDouble">The value to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, double aDouble)
            => aSeed.Hash(Convert.ToInt64(aDouble));

        /// <summary>
        /// Contributes a string to the developing HashCode seed.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aString">The value to contribute.</param>
        /// <param name="stringComparison">Optional comparison that creates the hash.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(
                this int aSeed,
                string aString,
                StringComparison stringComparison = StringComparison.Ordinal)
        {
            if (aString == null)
                return aSeed.Hash(0);
            switch (stringComparison) {
                case StringComparison.CurrentCulture :
                    return StringComparer.CurrentCulture.GetHashCode(aString);
                case StringComparison.CurrentCultureIgnoreCase :
                    return StringComparer.CurrentCultureIgnoreCase.GetHashCode(aString);
                case StringComparison.InvariantCulture :
                    return StringComparer.InvariantCulture.GetHashCode(aString);
                case StringComparison.InvariantCultureIgnoreCase :
                    return StringComparer.InvariantCultureIgnoreCase.GetHashCode(aString);
                case StringComparison.OrdinalIgnoreCase :
                    return StringComparer.OrdinalIgnoreCase.GetHashCode(aString);
                default :
                    return StringComparer.Ordinal.GetHashCode(aString);
            }
        }

        /// <summary>
        /// Contributes a possibly-null array to the developing HashCode seed.
        /// Each element may be a primitive, a reference, or a possibly-null array.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aArray">CAN be null.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, IEnumerable aArray)
        {
            if (aArray == null)
                return aSeed.Hash(0);
            int countPlusOne = 1; // So it differs from null
            foreach (object item in aArray) {
                ++countPlusOne;
                if (item is IEnumerable arrayItem) {
                    if (!object.ReferenceEquals(aArray, arrayItem))
                        aSeed = aSeed.Hash(arrayItem); // recursive call!
                } else
                    aSeed = aSeed.Hash(item);
            }
            return aSeed.Hash(countPlusOne);
        }

        /// <summary>
        /// Contributes a possibly-null array to the developing HashCode seed.
        /// You must provide the hash function for each element.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aArray">CAN be null.</param>
        /// <param name="hashElement">Required: yields the hash for each element
        /// in <paramref name="aArray"/>.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash<T>(this int aSeed, IEnumerable<T> aArray, Func<T, int> hashElement)
        {
            if (aArray == null)
                return aSeed.Hash(0);
            int countPlusOne = 1; // So it differs from null
            foreach (T item in aArray) {
                ++countPlusOne;
                aSeed = aSeed.Hash(hashElement(item));
            }
            return aSeed.Hash(countPlusOne);
        }

        /// <summary>
        /// Contributes a possibly-null object to the developing HashCode seed.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="aObject">CAN be null.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Hash(this int aSeed, object aObject)
        {
            switch (aObject) {
                case null :
                    return aSeed.Hash(0);
                case bool b :
                    return aSeed.Hash(b);
                case char c :
                    return aSeed.Hash(c);
                case int i :
                    return aSeed.Hash(i);
                case long l :
                    return aSeed.Hash(l);
                case float f :
                    return aSeed.Hash(f);
                case double d :
                    return aSeed.Hash(d);
                case string s :
                    return aSeed.Hash(s);
                case IEnumerable iEnumerable :
                    return aSeed.Hash(iEnumerable);
            }
            return aSeed.Hash(aObject.GetHashCode());
        }


        /// <summary>
        /// This utility method uses reflection to iterate all specified properties that are readable
        /// on the given object, excluding any property names given in the params arguments, and
        /// generates a hashcode.
        /// </summary>
        /// <param name="aSeed">The developing hash code, or the seed: if you have no seed, use
        /// the <see cref="Seed"/>.</param>
        /// <param name="aObject">CAN be null.</param>
        /// <param name="propertySelector"><see cref="BindingFlags"/> to select the properties to hash.</param>
        /// <param name="ignorePropertyNames">Optional.</param>
        /// <returns>A hash from the properties contributed to <c>aSeed</c>.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int HashAllProperties(
                this int aSeed,
                object aObject,
                BindingFlags propertySelector
                        = BindingFlags.Instance
                        | BindingFlags.Public
                        | BindingFlags.GetProperty,
                params string[] ignorePropertyNames)
        {
            if (aObject == null)
                return aSeed.Hash(0);
            if ((ignorePropertyNames != null)
                    && (ignorePropertyNames.Length != 0)) {
                foreach (PropertyInfo propertyInfo in aObject.GetType()
                        .GetProperties(propertySelector)) {
                    if (!propertyInfo.CanRead
                            || (Array.IndexOf(ignorePropertyNames, propertyInfo.Name) >= 0))
                        continue;
                    aSeed = aSeed.Hash(propertyInfo.GetValue(aObject));
                }
            } else {
                foreach (PropertyInfo propertyInfo in aObject.GetType()
                        .GetProperties(propertySelector)) {
                    if (propertyInfo.CanRead)
                        aSeed = aSeed.Hash(propertyInfo.GetValue(aObject));
                }
            }
            return aSeed;
        }


        /// <summary>
        /// NOTICE: this method is provided to contribute a <see cref="KeyValuePair{TKey,TValue}"/> to
        /// the developing HashCode seed; by hashing the key and the value independently. HOWEVER,
        /// this method has a different name since it will not be automatically invoked by
        /// <see cref="Hash(int,object)"/>, <see cref="Hash(int,IEnumerable)"/>,
        /// or <see cref="HashAllProperties"/> --- you MUST NOT mix this method with those unless
        /// you are sure that no KeyValuePair instances will be passed to those methods; or otherwise
        /// the generated hash code will not be consistent. This method itself ALSO will not invoke
        /// this method on the Key or Value here if that itself is a KeyValuePair.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="keyValuePair">The value to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int HashKeyAndValue<TKey, TValue>(this int aSeed, KeyValuePair<TKey, TValue> keyValuePair)
            => aSeed.Hash(keyValuePair.Key)
                    .Hash(keyValuePair.Value);

        /// <summary>
        /// NOTICE: this method is provided to contribute a collection of <see cref="KeyValuePair{TKey,TValue}"/>
        /// to the developing HashCode seed; by hashing the key and the value independently. HOWEVER,
        /// this method has a different name since it will not be automatically invoked by
        /// <see cref="Hash(int,object)"/>, <see cref="Hash(int,IEnumerable)"/>,
        /// or <see cref="HashAllProperties"/> --- you MUST NOT mix this method with those unless
        /// you are sure that no KeyValuePair instances will be passed to those methods; or otherwise
        /// the generated hash code will not be consistent. This method itself ALSO will not invoke
        /// this method on a Key or Value here if that itself is a KeyValuePair or an Enumerable of
        /// KeyValuePair.
        /// </summary>
        /// <param name="aSeed">The developing HashCode value or seed.</param>
        /// <param name="keyValuePairs">The values to contribute.</param>
        /// <returns>The new hash code.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int HashKeysAndValues<TKey, TValue>(
                this int aSeed,
                IEnumerable<KeyValuePair<TKey, TValue>> keyValuePairs)
        {
            if (keyValuePairs == null)
                return aSeed.Hash(null);
            foreach (KeyValuePair<TKey, TValue> keyValuePair in keyValuePairs) {
                aSeed = aSeed.HashKeyAndValue(keyValuePair);
            }
            return aSeed;
        }
    }
}

Yipes: मैं एक बग पाया! HashKeysAndValuesविधि है निर्धारित किया गया है: यह आह्वान HashKeyAndValue
स्टीवन कोको

0

मामले में आप polyfill करना चाहते HashCodeसेnetstandard2.1

public static class HashCode
{
    public static int Combine(params object[] instances)
    {
        int hash = 17;

        foreach (var i in instances)
        {
            hash = unchecked((hash * 31) + (i?.GetHashCode() ?? 0));
        }

        return hash;
    }
}

नोट: यदि इसका उपयोग किया जाता है struct, तो यह बॉक्सिंग के कारण मेमोरी आवंटित करेगा

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.