हैशसेट समानता के लिए तत्वों की तुलना कैसे करता है?


127

मेरे पास एक वर्ग है IComparable:

public class a : IComparable
{
    public int Id { get; set; }
    public string Name { get; set; }

    public a(int id)
    {
        this.Id = id;
    }

    public int CompareTo(object obj)
    {
        return this.Id.CompareTo(((a)obj).Id);
    }
}

जब मैं इस वर्ग की वस्तु की सूची को हैश सेट में जोड़ता हूं:

a a1 = new a(1);
a a2 = new a(2);
HashSet<a> ha = new HashSet<a>();
ha.add(a1);
ha.add(a2);
ha.add(a1);

सब कुछ ठीक है और ha.countहै 2, लेकिन:

a a1 = new a(1);
a a2 = new a(2);
HashSet<a> ha = new HashSet<a>();
ha.add(a1);
ha.add(a2);
ha.add(new a(1));

अब ha.countहै 3

  1. क्यों HashSetसम्मान aका CompareToतरीका नहीं है।
  2. क्या HashSetअद्वितीय वस्तुओं की सूची के लिए सबसे अच्छा तरीका है?

IEqualityComparer<T>कंस्ट्रक्टर में एक कार्यान्वयन जोड़ें या इसे कक्षा में लागू करें amsdn.microsoft.com/en-us/library/bb301504(v=vs.110).aspx
Jaider

जवाबों:


137

यह IEqualityComparer<T>( EqualityComparer<T>.Defaultजब तक आप निर्माण पर एक अलग निर्दिष्ट नहीं करते हैं) का उपयोग करता है ।

जब आप सेट में एक तत्व जोड़ते हैं, तो यह हैश कोड का उपयोग करके मिल जाएगा IEqualityComparer<T>.GetHashCode, और हैश कोड और तत्व दोनों को संग्रहीत करेगा (यह जांचने के बाद कि तत्व पहले से ही सेट में है, निश्चित रूप से)।

एक तत्व को देखने के लिए, यह पहले IEqualityComparer<T>.GetHashCodeहैश कोड को खोजने के लिए उपयोग करेगा , फिर समान हैश कोड वाले सभी तत्वों के लिए, यह IEqualityComparer<T>.Equalsवास्तविक समानता के लिए तुलना करने के लिए उपयोग करेगा ।

इसका मतलब है कि आपके पास दो विकल्प हैं:

  • IEqualityComparer<T>कंस्ट्रक्टर में एक कस्टम पास करें । यदि आप Tस्वयं को संशोधित नहीं कर सकते हैं , या यदि आप एक गैर-डिफ़ॉल्ट समानता संबंध चाहते हैं (जैसे "नकारात्मक उपयोगकर्ता आईडी वाले सभी उपयोगकर्ताओं को समान माना जाता है") तो यह सबसे अच्छा विकल्प है । यह लगभग कभी भी स्वयं प्रकार पर लागू नहीं होता है (अर्थात Fooलागू नहीं होता है IEqualityComparer<Foo>) लेकिन एक अलग प्रकार में जिसका उपयोग केवल तुलना के लिए किया जाता है।
  • ओवरराइड करके GetHashCodeऔर टाइप करके, स्वयं में समानता को लागू करें Equals(object)। आदर्श रूप IEquatable<T>में, प्रकार में भी लागू करें , खासकर अगर यह एक मूल्य प्रकार है। इन विधियों को डिफ़ॉल्ट समानता तुलनित्र द्वारा बुलाया जाएगा।

ध्यान दें कि आदेशित तुलना के संदर्भ में यह कैसे नहीं है - जो समझ में आता है, क्योंकि निश्चित रूप से ऐसी परिस्थितियां हैं जहां आप आसानी से समानता निर्दिष्ट कर सकते हैं लेकिन कुल आदेश नहीं। यह सभी समान है Dictionary<TKey, TValue>, मूल रूप से।

यदि आप एक ऐसा सेट चाहते हैं जो सिर्फ समानता तुलना के बजाय ऑर्डर का उपयोग करता है , तो आपको SortedSet<T>.NET 4 से उपयोग करना चाहिए - जो आपको IComparer<T>इसके बजाय निर्दिष्ट करने की अनुमति देता है IEqualityComparer<T>। यह उपयोग करेगा IComparer<T>.Compare- जो आप का उपयोग कर रहे हैं IComparable<T>.CompareToया IComparable.CompareToयदि आप को सौंप देंगे Comparer<T>.Default


7
+1 यह भी ध्यान दें @ tyriker का जवाब (कि IMO यहां एक टिप्पणी होनी चाहिए) जो बताती है कि लीवरेज का सबसे सरल तरीका यह है कि IEqualityComparer<T>.GetHashCode/Equals()आप इसे लागू करें Equalsऔर खुद GetHashCodeपर T(और जब आप ऐसा कर रहे हों, तो आप भी जोरदार टाइप किए गए समकक्ष को लागू करेंगे। : - bool IEquatable<T>.Equals(T other))
रूबेन बार्टलिंक

5
हालांकि बहुत सटीक यह उत्तर कुछ हद तक भ्रमित करने वाला हो सकता है, विशेष रूप से नए उपयोगकर्ताओं के लिए क्योंकि यह स्पष्ट रूप से यह नहीं बताता है कि सबसे सरल मामले में ओवरराइडिंग के लिए Equalsऔर GetHashCodeपर्याप्त है - जैसा कि @ tyriker के उत्तर में उल्लेख किया गया है।
बार्टोज़केपी

एक बार जब आप लागू करते हैं IComparable(या IComparerउस मामले के लिए) तो आपको अलग से समानता (लेकिन सिर्फ GetHashCode) को लागू करने के लिए नहीं कहा जाना चाहिए । एक मायने में तुल्यता इंटरफेस को समानता इंटरफेस से विरासत में मिला होना चाहिए। मैं दो अलग-अलग कार्यों को करने में प्रदर्शन के लाभ को समझता हूं (जहां आप यह कहकर समानता को अलग-अलग रूपांतरित कर सकते हैं कि क्या कुछ समान है या नहीं) लेकिन फिर भी .. बहुत भ्रामक अन्यथा जब आपने निर्दिष्ट किया है कि जब CompareToफ़ंक्शन फ़ंक्शन में समान हैं और फ्रेमवर्क पर विचार नहीं करते हैं उस।
नवफाल

@nawfal में हर चीज का तार्किक क्रम नहीं है। यदि आप दो चीजों की तुलना कर रहे हैं, जिसमें एक बूल संपत्ति है, तो यह सिर्फ सादा भयानक है जैसे कुछ लिखना है a.boolProp == b.boolProp ? 1 : 0या यह होना चाहिए a.boolProp == b.boolProp ? 0 : -1या a.boolProp == b.boolProp ? 1 : -1। युक!
साइमन_विवर

1
@Simon_Weaver यह है। मैं किसी भी तरह से अपनी काल्पनिक सुविधा से बचना चाहता था जिसे मैं प्रस्तावित कर रहा था।
नवाफ

77

यहां उस उत्तर के एक भाग पर स्पष्टीकरण दिया गया है जो अनसुना कर दिया गया है: आपके प्रकार के ऑब्जेक्ट HashSet<T>को लागू नहीं करना है, IEqualityComparer<T>बल्कि इसके बजाय केवल ओवरराइड करना है Object.GetHashCode()और Object.Equals(Object obj)

इसके अलावा:

public class a : IEqualityComparer<a>
{
  public int GetHashCode(a obj) { /* Implementation */ }
  public bool Equals(a obj1, a obj2) { /* Implementation */ }
}

तुम इसे करो:

public class a
{
  public override int GetHashCode() { /* Implementation */ }
  public override bool Equals(object obj) { /* Implementation */ }
}

यह सूक्ष्म है, लेकिन इसने एक दिन के बेहतर हिस्से के लिए मुझे टाल दिया, ताकि यह सुनिश्चित करने के लिए कि हमसैट को काम करने का प्रयास किया जाए। और जैसा दूसरों ने कहा है, सेट के साथ काम करते समय HashSet<a>कॉलिंग a.GetHashCode()और a.Equals(obj)आवश्यक समाप्त हो जाएगा ।


2
अच्छी बात। BTW के रूप में मेरी टिप्पणी पर @ JonSkeet के जवाब में उल्लेख किया गया है, आपको bool IEquatable<T>.Equals(T other)थोड़ी दक्षता हासिल करने के लिए भी लागू करना चाहिए, लेकिन अधिक स्पष्टता लाभ। Obv कारणों के लिए, GetHashCodeसाथ में लागू करने की आवश्यकता के अलावा IEquatable<T>, IEquatable <T> के लिए डॉक्टर का उल्लेख है कि निरंतरता प्रयोजनों के object.Equalsलिए भी आप स्थिरता के लिए ओवरराइड करना चाहिए
Ruben बार्टेलिंक

मैंने इसे लागू करने की कोशिश की। ovveride getHashcodeकाम करता है, लेकिन override bool equalsत्रुटि हो जाता है: कोई विधि ओवरराइड करने के लिए मिला। कोई उपाय?
स्टेफनवेड्स

अंत में जानकारी की मुझे तलाश थी। धन्यवाद।
मौरो सेम्पीटरो

उपरोक्त उत्तर पर मेरी टिप्पणियों से - आपके "मामले में" के बजाय, आप कर सकते थे public class a : IEqualityComparer<a> {, और फिर new HashSet<a>(a)
हांकना

लेकिन ऊपर जॉन कंकाल की टिप्पणियों को देखें।
हांकका

9

HashSetका उपयोग करता है Equalsऔर GetHashCode()

CompareTo आदेशित सेट के लिए है।

यदि आप अद्वितीय ऑब्जेक्ट चाहते हैं, लेकिन आप उनके पुनरावृत्ति क्रम की परवाह नहीं करते हैं, HashSet<T>तो आमतौर पर सबसे अच्छा विकल्प होता है।


5

कंस्ट्रक्टर हैशसेट को ऑब्जेक्ट मिलता है जो नई वस्तु को जोड़ने के लिए IEqualityComparer लागू करता है। यदि आप HashSet में प्रयोग विधि का उपयोग करते हैं, तो आप बराबरी, GetHashCode को ओवरराइड करते हैं

namespace HashSet
{
    public class Employe
    {
        public Employe() {
        }

        public string Name { get; set; }

        public override string ToString()  {
            return Name;
        }

        public override bool Equals(object obj) {
            return this.Name.Equals(((Employe)obj).Name);
        }

        public override int GetHashCode() {
            return this.Name.GetHashCode();
        }
    }

    class EmployeComparer : IEqualityComparer<Employe>
    {
        public bool Equals(Employe x, Employe y)
        {
            return x.Name.Trim().ToLower().Equals(y.Name.Trim().ToLower());
        }

        public int GetHashCode(Employe obj)
        {
            return obj.Name.GetHashCode();
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            HashSet<Employe> hashSet = new HashSet<Employe>(new EmployeComparer());
            hashSet.Add(new Employe() { Name = "Nik" });
            hashSet.Add(new Employe() { Name = "Rob" });
            hashSet.Add(new Employe() { Name = "Joe" });
            Display(hashSet);
            hashSet.Add(new Employe() { Name = "Rob" });
            Display(hashSet);

            HashSet<Employe> hashSetB = new HashSet<Employe>(new EmployeComparer());
            hashSetB.Add(new Employe() { Name = "Max" });
            hashSetB.Add(new Employe() { Name = "Solomon" });
            hashSetB.Add(new Employe() { Name = "Werter" });
            hashSetB.Add(new Employe() { Name = "Rob" });
            Display(hashSetB);

            var union = hashSet.Union<Employe>(hashSetB).ToList();
            Display(union);
            var inter = hashSet.Intersect<Employe>(hashSetB).ToList();
            Display(inter);
            var except = hashSet.Except<Employe>(hashSetB).ToList();
            Display(except);

            Console.ReadKey();
        }

        static void Display(HashSet<Employe> hashSet)
        {
            if (hashSet.Count == 0)
            {
                Console.Write("Collection is Empty");
                return;
            }
            foreach (var item in hashSet)
            {
                Console.Write("{0}, ", item);
            }
            Console.Write("\n");
        }

        static void Display(List<Employe> list)
        {
            if (list.Count == 0)
            {
                Console.WriteLine("Collection is Empty");
                return;
            }
            foreach (var item in list)
            {
                Console.Write("{0}, ", item);
            }
            Console.Write("\n");
        }
    }
}

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