LINQ: Nilai yang berbeda

137

Saya memiliki item berikut yang ditetapkan dari XML:

id           category

5            1
5            3
5            4
5            3
5            3

Saya memerlukan daftar berbeda dari item ini:

5            1
5            3
5            4

Bagaimana saya bisa membedakan Kategori DAN Id juga di LINQ?

balint
sumber

Jawaban:

223

Apakah Anda mencoba untuk tampil beda dengan lebih dari satu bidang? Jika demikian, cukup gunakan tipe anonim dan operator Distinct dan seharusnya tidak masalah:

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

Jika Anda mencoba mendapatkan sekumpulan nilai yang berbeda dari tipe "lebih besar", tetapi hanya melihat beberapa subset properti untuk aspek perbedaan, Anda mungkin ingin DistinctByseperti yang diterapkan di MoreLINQ di 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;
         }
     }
 }

(Jika Anda masuk nullsebagai pembanding, itu akan menggunakan pembanding default untuk jenis kunci.)

Jon Skeet
sumber
Oh, jadi dengan "tipe yang lebih besar" yang Anda maksud mungkin saya masih menginginkan semua properti dalam hasil meskipun saya hanya ingin membandingkan beberapa properti untuk menentukan perbedaan?
The Red Pea
@TheRedPea: Ya, persis.
Jon Skeet
27

Selain jawaban Jon Skeet, Anda juga dapat menggunakan grup menurut ekspresi untuk mendapatkan grup unik bersama dengan hitungan untuk setiap iterasi grup:

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() };
James Alexander
sumber
4
Anda menulis "selain jawaban Jon Skeet" ... Saya tidak tahu apakah hal seperti itu mungkin. ;)
Yehuda Makarov
13

Untuk siapa pun yang masih mencari; inilah cara lain untuk menerapkan pembanding lambda khusus.

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

Anda kemudian dapat membuat ekstensi untuk Linq Distinct yang dapat menggunakan lambda's

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

Pemakaian:

var availableItems = list.Distinct((p, p1) => p.Id== p1.Id);
Ricky G
sumber
Melihat sumber referensi, Distinct menggunakan set hash untuk menyimpan elemen yang telah dihasilkannya. Selalu mengembalikan kode hash yang sama berarti setiap elemen yang dikembalikan sebelumnya diperiksa setiap saat. Kode hash yang lebih kuat akan mempercepat karena hanya akan dibandingkan dengan elemen dalam keranjang hash yang sama. Nol adalah default yang masuk akal, tetapi mungkin perlu mendukung lambda kedua untuk kode hash.
Darryl
Poin bagus! Saya akan mencoba mengedit ketika saya punya waktu, jika Anda sedang bekerja di domain ini saat ini, silakan edit
Ricky G
8

Saya agak terlambat untuk jawabannya, tetapi Anda mungkin ingin melakukan ini jika Anda menginginkan seluruh elemen, tidak hanya nilai yang ingin Anda kelompokkan dengan:

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

Ini akan memberi Anda seluruh elemen pertama yang cocok dengan grup Anda dengan pilihan, seperti contoh kedua Jon Skeets menggunakan DistinctBy, tetapi tanpa menerapkan pembanding IEqualityComparer. DistinctBy kemungkinan besar akan lebih cepat, tetapi solusi di atas akan melibatkan lebih sedikit kode jika kinerja tidak menjadi masalah.

Olle Johansson
sumber
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)); 
}
Mohamed Elsayed
sumber
1

Karena kita berbicara tentang memiliki setiap elemen tepat satu kali, "set" lebih masuk akal bagi saya.

Contoh dengan penerapan kelas dan IEqualityComparer:

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

Sekarang

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

setList akan memiliki elemen unik

Saya memikirkan hal ini saat berurusan dengan .Except()yang mengembalikan perbedaan-set

Aditya AVS
sumber