2016-02-01 11 views
6

GroupBy() और Count() > 1 का उपयोग करके मैं एक सूची में अपनी कक्षा के डुप्लिकेट उदाहरण ढूंढने की कोशिश कर रहा हूं।जटिल वस्तु पर GroupBy (उदा। सूची <T>)

वर्ग इस तरह दिखता है:

public class SampleObject 
{ 
    public string Id; 
    public IEnumerable<string> Events; 
} 

और यह कैसे मैं और समूह का दृष्टांत सूची है:

public class Program 
{ 
    private static void Main(string[] args) 
    { 
     var items = new List<SampleObject>() 
     { 
      new SampleObject() { Id = "Id", Events = new List<string>() { "ExampleEvent" } }, 
      new SampleObject() { Id = "Id", Events = new List<string>() { "ExampleEvent" } } 
     }; 

     var duplicates = items.GroupBy(x => new { Token = x.Id, x.Events }) 
         .Where(g => g.Count() > 1) 
         .Select(g => g.Key) 
         .ToList(); 
    } 
} 

duplicates कोई आइटम नहीं होता। मैं समूहबद्ध काम कैसे कर सकता हूं?

+2

डिफ़ॉल्ट सूचियों द्वारा अपने आइटम के मूल्यों से तुलना नहीं कर रहे हैं। –

+1

@ सेर्गी बेरेज़ोवस्कीय - जो यहां मुद्दा नहीं है। समस्या 'गेटहाशकोड' और 'बराबर' ओवरराइड की कमी है। – Enigmativity

+3

@ निष्क्रियता 'नया {टोकन = x.Id, x.Events}' के बराबर ओवरराइड और 'नमूना ऑब्जेक्ट' के GetHashCode को ओवरराइड करने के लिए कुछ भी नहीं है। यहां समस्या पूरी तरह से 'x.Events' तुलना –

उत्तर

7

वस्तुओं LINQ के ऑपरेटरों के कई के साथ काम करने में इस तरह के GroupBy या Distinct के रूप में, आप या तो GetHashCode & Equals को लागू करना चाहिए पाने के लिए, या आप एक कस्टम comparer प्रदान करनी चाहिए।

आपके मामले में, एक सूची के रूप में एक संपत्ति के साथ आपको शायद तुलनात्मक की आवश्यकता होती है, जब तक कि आप केवल सूची को पढ़ नहीं लेते।

इस comparer का प्रयास करें:

public class SampleObjectComparer : IEqualityComparer<SampleObject> 
{ 
    public bool Equals(SampleObject x, SampleObject y) 
    { 
     return x.Id == y.Id && x.Events.SequenceEqual(y.Events); 
    } 

    public int GetHashCode(SampleObject x) 
    { 
     return x.Id.GetHashCode()^x.Events.Aggregate(0, (a, y) => a^y.GetHashCode()); 
    } 
} 

अब इस कोड काम करता है:

var items = new List<SampleObject>() 
    { 
     new SampleObject() { Id = "Id", Events = new List<string>() { "ExampleEvent"} }, 
     new SampleObject() { Id = "Id", Events = new List<string>() { "ExampleEvent" } } 
    }; 

    var comparer = new SampleObjectComparer(); 

    var duplicates = items.GroupBy(x => x, comparer) 
        .Where(g => g.Count() > 1) 
        .Select(g => g.Key) 
        .ToList(); 
1

List<T> कोई ओवरराइड Equals + GetHashCode है, यही कारण है कि अपने GroupBy अपेक्षा के अनुरूप काम नहीं करता है। अज्ञात प्रकार के दो गुणों में से एक सूची को संदर्भित करता है, जब GroupBy को दो सूचियों की तुलना करना पड़ता है Object.RefernceEquals का उपयोग किया जाता है जो केवल जांच करता है कि दोनों एक ही संदर्भ हैं और नहीं, दोनों में नमूना तत्व होते हैं।

public class IdEventComparer : IEqualityComparer<SampleObject> 
{ 
    public bool Equals(SampleObject x, SampleObject y) 
    { 
     if (object.ReferenceEquals(x, y)) 
      return true; 
     if (x == null || y == null) 
      return false; 
     if(x.Id != y.Id) 
      return false; 
     if (x.Events == null && y.Events == null) 
      return true; 
     if (x.Events == null || y.Events == null) 
      return false; 

     return x.Events.SequenceEqual(y.Events); 
    } 

    public int GetHashCode(SampleObject obj) 
    { 
     if(obj == null) return 23; 
     unchecked 
     { 
      int hash = 23; 
      hash = (hash * 31) + obj.Id == null ? 31 : obj.Id.GetHashCode(); 

      if (obj.Events == null) return hash; 
      foreach (string item in obj.Events) 
      { 
       hash = (hash * 31) + (item == null ? 0 : item.GetHashCode()); 
      } 
      return hash; 
     } 
    } 
} 

तो फिर तुम यह कई LINQ तरीकों में भी GroupBy की तरह उपयोग कर सकते हैं:

आप एक कस्टम IEqualityComparer<T> प्रदान कर सकता है

var duplicates = items.GroupBy(x => x, new IdEventComparer()) 
    .Where(g => g.Count() > 1) 
    .Select(g => g.Key) 
    .ToList(); 
1

GroupBy() एक डिफ़ॉल्ट तुलना प्रदर्शन करेंगे, यह पता लगाने के लिए पैदा कर रहा अपने सूची बराबर नहीं है।

var eventList1 = new List<string>() { "ExampleEvent" }; 
var eventList2 = new List<string>() { "ExampleEvent" }; 

Console.WriteLine(eventList1.GetHashCode()); 
Console.WriteLine(eventList2.GetHashCode()); 
Console.WriteLine(eventList1.Equals(eventList2)); 

दो "बराबर" सूचियों, सही:

निम्नलिखित कोड देखें? हालांकि, यह प्रिंट करेगा:

796641852 
1064243573 
False 

तो उन्हें बराबर नहीं माना जाता है, इसलिए समूहित नहीं किया जाता है।

आपको एक कस्टम तुलनाकर्ता प्रदान करने की आवश्यकता है, जो वस्तुओं के प्रासंगिक गुणों की तुलना करेगा। ध्यान दें कि जैसा कि पहले दिखाया गया है, List<T>.GetHashCode() सूची में आइटम का सही ढंग से प्रतिनिधित्व नहीं करता है।

public class SampleObjectComparer : IEqualityComparer<SampleObject> 
{ 
    public bool Equals(SampleObject a, SampleObject b) 
    { 
     return a.Id == b.Id 
      && a.Events.SequenceEqual(b.Events); 
    } 

    public int GetHashCode(SampleObject a) 
    { 
     int hash = 17; 

     hash = hash * 23 + a.Id.GetHashCode(); 

     foreach (var evt in a.Events) 
     { 
      hash = hash * 31 + evt.GetHashCode(); 
     }   

     return hash; 
    } 
} 

और यह इस तरह का उपयोग करें::

आपको लगता है कि इस तरह के रूप (Good GetHashCode() override for List of Foo objects respecting the order से और LINQ GroupBy on multiple ref-type fields; Custom EqualityComparer) कर सकते हैं

var eventList1 = new List<string>() { "ExampleEvent" }; 
var eventList2 = new List<string>() { "ExampleEvent" }; 

var items = new List<SampleObject>() 
{ 
    new SampleObject() { Id = "Id", Events = eventList1 }, 
    new SampleObject() { Id = "Id", Events = eventList2 } 
}; 

var duplicates = items.GroupBy(x => x, new SampleObjectComparer()) 
       .Where(g => g.Count() > 1) 
       .Select(g => g.Key) 
       .ToList(); 

Console.WriteLine(duplicates.Count); 
संबंधित मुद्दे