2016-02-01 14 views
6

Sử dụng GroupBy()Count() > 1 Tôi đang cố gắng tìm các bản sao trùng lặp của lớp trong danh sách.GroupBy trên đối tượng phức tạp (ví dụ: Danh sách <T>)

Lớp trông như thế này:

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

Và đây là cách tôi nhanh chóng và nhóm danh sách:

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

Các duplicates chứa có mặt hàng nào. Làm thế nào tôi có thể thực hiện công việc nhóm?

+2

By Chức năng mặc định không được so sánh bằng giá trị các mặt hàng của họ. –

+1

@SergeyBerezovskiy - Đó không phải là vấn đề ở đây. Vấn đề là thiếu 'GetHashCode' và' Equals' override. – Enigmativity

+3

@Enigmativity 'mới {Token = x.Id, x.Events}' không có gì để ghi đè Equals và GetHashCode của 'SampleObject'. Vấn đề ở đây là hoàn toàn gây ra bởi sự so sánh 'x.Events' –

Trả lời

7

Để có được đối tượng để làm việc với nhiều nhà khai thác LINQ, chẳng hạn như GroupBy hoặc Distinct, bạn phải thực hiện GetHashCode & Equals, hoặc bạn phải cung cấp một comparer tùy chỉnh.

Trong trường hợp của bạn, với thuộc tính dưới dạng danh sách bạn có thể cần so sánh, trừ khi bạn đã tạo danh sách chỉ đọc.

Hãy thử Comparer này:

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

Bây giờ mã này hoạt động:

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> không có ghi đè Equals + GetHashCode, đó là lý do tại sao GroupBy của bạn không hoạt động như mong đợi. Một trong hai thuộc tính của kiểu ẩn danh tham chiếu đến danh sách, khi GroupBy phải so sánh hai danh sách Object.RefernceEquals được sử dụng chỉ kiểm tra xem cả hai có cùng tham chiếu hay không nếu cả hai đều chứa các phần tử mẫu.

Bạn có thể cung cấp một tùy chỉnh IEqualityComparer<T>:

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

Sau đó, bạn có thể sử dụng nó trong nhiều phương pháp LINQ như cũng GroupBy:

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

GroupBy() sẽ thực hiện một sự so sánh mặc định, gây ra nó để tìm bạn danh sách không bằng nhau.

Xem đoạn mã sau:

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

Hai "bình đẳng" danh sách, phải không? Tuy nhiên, điều này sẽ in:

796641852 
1064243573 
False 

Vì vậy, họ không coi là bình đẳng, do đó không được nhóm lại.

Bạn cần cung cấp bộ so sánh tùy chỉnh, sẽ so sánh các thuộc tính có liên quan của các đối tượng. Lưu ý rằng như được hiển thị trước đây, List<T>.GetHashCode() không đại diện đúng các mục trong danh sách.

Bạn có thể làm điều đó như thế (từ Good GetHashCode() override for List of Foo objects respecting the orderLINQ GroupBy on multiple ref-type fields; Custom EqualityComparer):

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

Và sử dụng nó như thế này:

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); 
Các vấn đề liên quan