LINQ: Giá trị khác biệt


136

Tôi có bộ mục sau được đặt từ XML:

id           category

5            1
5            3
5            4
5            3
5            3

Tôi cần một danh sách riêng biệt của các mục này:

5            1
5            3
5            4

Làm cách nào tôi có thể phân biệt cho Danh mục VÀ Id trong LINQ?

Câu trả lời:


221

Bạn đang cố gắng để được khác biệt bởi nhiều hơn một lĩnh vực? Nếu vậy, chỉ cần sử dụng một loại ẩn danh và toán tử riêng biệt và nó sẽ ổn:

var query = doc.Elements("whatever")
               .Select(element => new {
                             id = (int) element.Attribute("id"),
                             category = (int) element.Attribute("cat") })
               .Distinct();

Nếu bạn đang cố gắng để có được một tập hợp các giá trị riêng biệt của loại "lớn hơn", nhưng chỉ nhìn vào một số tập hợp thuộc tính cho khía cạnh khác biệt, bạn có thể muốn được DistinctBytriển khai trong MoreLINQ trong DistinctBy.cs:

 public static IEnumerable<TSource> DistinctBy<TSource, TKey>(
     this IEnumerable<TSource> source,
     Func<TSource, TKey> keySelector,
     IEqualityComparer<TKey> comparer)
 {
     HashSet<TKey> knownKeys = new HashSet<TKey>(comparer);
     foreach (TSource element in source)
     {
         if (knownKeys.Add(keySelector(element)))
         {
             yield return element;
         }
     }
 }

(Nếu bạn chuyển qua nulllàm bộ so sánh, nó sẽ sử dụng bộ so sánh mặc định cho loại khóa.)


Ồ, vậy là "loại lớn hơn" bạn có thể có nghĩa là tôi vẫn muốn tất cả các thuộc tính trong kết quả mặc dù tôi chỉ muốn so sánh một vài thuộc tính để xác định tính khác biệt?
Hạt đậu đỏ

@TheRedPea: Vâng, chính xác.
Jon Skeet


27

Ngoài câu trả lời của Jon Skeet, bạn cũng có thể sử dụng nhóm theo biểu thức để có được các nhóm duy nhất cùng với số lần lặp cho mỗi nhóm:

var query = from e in doc.Elements("whatever")
            group e by new { id = e.Key, val = e.Value } into g
            select new { id = g.Key.id, val = g.Key.val, count = g.Count() };

4
Bạn đã viết "ngoài câu trả lời của Jon Skeet" ... Tôi không biết liệu điều đó có khả thi hay không. ;)
Yehuda Makarov

13

Đối với bất kỳ ai vẫn đang tìm kiếm; Đây là một cách khác để thực hiện một so sánh lambda tùy chỉnh.

public class LambdaComparer<T> : IEqualityComparer<T>
    {
        private readonly Func<T, T, bool> _expression;

        public LambdaComparer(Func<T, T, bool> lambda)
        {
            _expression = lambda;
        }

        public bool Equals(T x, T y)
        {
            return _expression(x, y);
        }

        public int GetHashCode(T obj)
        {
            /*
             If you just return 0 for the hash the Equals comparer will kick in. 
             The underlying evaluation checks the hash and then short circuits the evaluation if it is false.
             Otherwise, it checks the Equals. If you force the hash to be true (by assuming 0 for both objects), 
             you will always fall through to the Equals check which is what we are always going for.
            */
            return 0;
        }
    }

sau đó bạn có thể tạo tiện ích mở rộng cho linq Phân biệt có thể có trong lambda's

   public static IEnumerable<T> Distinct<T>(this IEnumerable<T> list,  Func<T, T, bool> lambda)
        {
            return list.Distinct(new LambdaComparer<T>(lambda));
        }  

Sử dụng:

var availableItems = list.Distinct((p, p1) => p.Id== p1.Id);

Nhìn vào nguồn tham chiếu, Distinc sử dụng một bộ băm để lưu trữ các phần tử mà nó đã mang lại. Luôn trả về cùng mã băm có nghĩa là mọi phần tử được trả về trước đó đều được kiểm tra mỗi lần. Mã băm mạnh hơn sẽ tăng tốc mọi thứ vì nó chỉ so sánh với các phần tử trong cùng nhóm băm. Zero là một mặc định hợp lý, nhưng nó có thể đáng để hỗ trợ lambda thứ hai cho mã băm.
Darryl

Điểm tốt! Tôi sẽ thử chỉnh sửa khi tôi có thời gian, nếu bạn đang làm việc trong miền này vào lúc này, vui lòng chỉnh sửa
Ricky G

8

Tôi hơi muộn với câu trả lời, nhưng bạn có thể muốn làm điều này nếu bạn muốn toàn bộ phần tử, không chỉ các giá trị bạn muốn nhóm theo:

var query = doc.Elements("whatever")
               .GroupBy(element => new {
                             id = (int) element.Attribute("id"),
                             category = (int) element.Attribute("cat") })
               .Select(e => e.First());

Điều này sẽ cung cấp cho bạn toàn bộ phần tử đầu tiên phù hợp với nhóm của bạn bằng cách chọn, giống như ví dụ thứ hai của Jon Skeets bằng cách sử dụng DistincBy, nhưng không thực hiện trình so sánh IEqualityComparer. DistincBy rất có thể sẽ nhanh hơn, nhưng giải pháp ở trên sẽ liên quan đến ít mã hơn nếu hiệu suất không phải là vấn đề.


4
// First Get DataTable as dt
// DataRowComparer Compare columns numbers in each row & data in each row

IEnumerable<DataRow> Distinct = dt.AsEnumerable().Distinct(DataRowComparer.Default);

foreach (DataRow row in Distinct)
{
    Console.WriteLine("{0,-15} {1,-15}",
        row.Field<int>(0),
        row.Field<string>(1)); 
}

0

Vì chúng ta đang nói về việc có mọi yếu tố chính xác một lần, một "bộ" có ý nghĩa hơn đối với tôi.

Ví dụ với các lớp và IEqualityComparer đã triển khai:

 public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public Product(int x, string y)
        {
            Id = x;
            Name = y;
        }
    }

    public class ProductCompare : IEqualityComparer<Product>
    {
        public bool Equals(Product x, Product y)
        {  //Check whether the compared objects reference the same data.
            if (Object.ReferenceEquals(x, y)) return true;

            //Check whether any of the compared objects is null.
            if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
                return false;

            //Check whether the products' properties are equal.
            return x.Id == y.Id && x.Name == y.Name;
        }
        public int GetHashCode(Product product)
        {
            //Check whether the object is null
            if (Object.ReferenceEquals(product, null)) return 0;

            //Get hash code for the Name field if it is not null.
            int hashProductName = product.Name == null ? 0 : product.Name.GetHashCode();

            //Get hash code for the Code field.
            int hashProductCode = product.Id.GetHashCode();

            //Calculate the hash code for the product.
            return hashProductName ^ hashProductCode;
        }
    }

Hiện nay

List<Product> originalList = new List<Product> {new Product(1, "ad"), new Product(1, "ad")};
var setList = new HashSet<Product>(originalList, new ProductCompare()).ToList();

setList sẽ có các yếu tố độc đáo

Tôi nghĩ về điều này trong khi xử lý .Except()mà trả về một sự khác biệt thiết lập

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.