Belirli bir mülk üzerindeki LINQ's Distinct ()


1094

Bunu öğrenmek için LINQ ile oynuyorum, ancak Distinctbasit bir listeye sahip olmadığımda nasıl kullanılacağını anlayamıyorum (tamsayıların basit bir listesini yapmak oldukça kolay, bu soru değil). Nesnenin bir veya daha fazla özelliğindeki bir nesne listesinde Distinct kullanmak istersem ne olur ?

Örnek: Nesne PersonÖzellik ile ise Id. Tüm Kişiyi nasıl alabilirim ve nesnenin Distinctmülkiyeti ile nasıl kullanabilirim Id?

Person1: Id=1, Name="Test1"
Person2: Id=1, Name="Test1"
Person3: Id=2, Name="Test2"

Nasıl Person1ve nasıl alabilirim Person3? Mümkün mü?

LINQ ile mümkün değilse Person, .NET 3.5'teki bazı özelliklerine bağlı olarak bir listeye sahip olmanın en iyi yolu nedir?

Yanıtlar:


1246

EDIT : Bu artık MoreLINQ bir parçasıdır .

İhtiyacınız olan şey etkili bir şekilde "farklı" dır. Yazmak oldukça kolay olmasına rağmen, LINQ'nun bir parçası olduğuna inanmıyorum:

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

Yalnızca Idözelliği kullanarak farklı değerleri bulmak için şunları kullanabilirsiniz:

var query = people.DistinctBy(p => p.Id);

Birden çok özellik kullanmak için, eşitliği uygun şekilde uygulayan anonim türleri kullanabilirsiniz:

var query = people.DistinctBy(p => new { p.Id, p.Name });

Test edilmedi, ancak işe yaramalı (ve şimdi en azından derliyor).

Anahtarlar için varsayılan karşılaştırıcıyı varsayar - bir eşitlik karşılaştırıcısını geçmek istiyorsanız, bunu yapıcıya aktarın HashSet.



1
@ ashes999: Ne demek istediğinden emin değilim. Kod, bağımlılıktan memnun olup olmadığınıza bağlı olarak yanıtta ve kütüphanede bulunur.
Jon Skeet

10
@ ashes999: Bunu sadece tek bir yerde yapıyorsanız, o zaman emin olun, GroupBykullanımı daha basittir. Birden fazla yerde ihtiyacınız varsa, amacı kapsüllemek çok daha temizdir (IMO).
Jon Skeet

5
@MatthewWhited: IQueryable<T>Burada bahsedilmediğinden, bunun ne kadar alakalı olduğunu göremiyorum. Bunun EF vb. İçin uygun olmayacağına katılıyorum, ancak LINQ to Objects içinde daha uygun olduğunu düşünüyorum GroupBy. Sorunun bağlamı her zaman önemlidir.
Jon Skeet

7
Proje github'a
Phate01

1858

Bir veya daha fazla mülke dayalı ayrı bir liste almak istersem ne olur ?

Basit! Onları gruplandırmak ve gruptan bir kazanan seçmek istiyorsunuz.

List<Person> distinctPeople = allPeople
  .GroupBy(p => p.PersonId)
  .Select(g => g.First())
  .ToList();

Birden çok mülk üzerindeki grupları tanımlamak istiyorsanız, şu şekilde yapabilirsiniz:

List<Person> distinctPeople = allPeople
  .GroupBy(p => new {p.PersonId, p.FavoriteColor} )
  .Select(g => g.First())
  .ToList();

1
@ErenErsonmez emin. Gönderilen kodumla, ertelenmiş yürütme isteniyorsa ToList çağrısını bırakın.
Amy B

5
Çok güzel bir cevap! Realllllly Linq-to-Entities bana görünümü değiştiremedim bir sql görünümden tahrik yardımcı oldu. First () yerine FirstOrDefault () kullanmam gerekiyordu - hepsi iyi.
Alex KeySmith

8
Denedim ve Seç (g => g.FirstOrDefault ()) olarak

26
@ChocapicSz Hayır. Kaynakta birden fazla öğe olduğunda her ikisi Single()ve SingleOrDefault()her biri atar. Bu operasyonda, her grubun birden fazla eşyaya sahip olma ihtimalini bekliyoruz. Bu nedenle, her grubun en az bir üyesi olması gerekir, çünkü her grubun en az bir üyesi ve talepleri olduğunu anlayamayan EntityFramework kullanmıyorsanız First()tercih edilir . FirstOrDefault()FirstOrDefault()
Amy B

2
FirstOrDefault() Github.com/dotnet/efcore/issues/12088 kullanarak bile şu anda EF Core'da desteklenmiyor gibi görünüyor 3.1 sürümündeyim ve hataları " çeviremiyorum ".
Collin M. Barrett

78

kullanın:

List<Person> pList = new List<Person>();
/* Fill list */

var result = pList.Where(p => p.Name != null).GroupBy(p => p.Id).Select(grp => grp.FirstOrDefault());

whereEğer girişlerini filtre yardımcı olur (daha karmaşık olabilir) ve groupbyve selectayrı bir işlevi yerine getirir.


1
Mükemmel ve Linq'i genişletmeden veya başka bir bağımlılık kullanmadan çalışır.
19:19

77

Ayrıca tüm LINQ benzeri görünmesini istiyorsanız sorgu sözdizimini de kullanabilirsiniz:

var uniquePeople = from p in people
                   group p by new {p.ID} //or group by new {p.ID, p.Name, p.Whatever}
                   into mygroup
                   select mygroup.FirstOrDefault();

4
Hmm düşüncelerim hem sorgu sözdizimi hem de akıcı API sözdizimi gibi LINQ gibi ve insanların kullandığı tercihler. Akıcı API'yi kendim tercih ediyorum, bu yüzden daha fazla LINK-Like düşünecektim ama o zaman sanırım öznel
Max Carroll

LINQ-Like tercih ile ilgisi yoktur, "LINQ-benzeri" olmak farklı bir sorgu dili C # gömülü gibi bakmak ile ilgisi var, akıcı arayüzü tercih, java akışlarından geliyor, ama LINQ-benzeri DEĞİL.
Ryan The Leach

Mükemmel!! Kahramanımsın!
Farzin Kanzi

63

Bence yeterli:

list.Select(s => s.MyField).Distinct();

43
Ya sadece o alanı değil, tüm nesnesini geri alması gerekiyorsa?
Festim Cahani

1
Aynı özellik değerine sahip birkaç nesnenin tam olarak ne nesnesidir?
donRumatta

40

İlk önce alanlarınıza göre gruplandırın, ardından firstordefault öğesini seçin.

    List<Person> distinctPeople = allPeople
   .GroupBy(p => p.PersonId)
   .Select(g => g.FirstOrDefault())
   .ToList();

26

Bunu standartla yapabilirsiniz Linq.ToLookup(). Bu, her benzersiz anahtar için bir değerler koleksiyonu oluşturur. Koleksiyondaki ilk öğeyi seçmeniz yeterlidir

Persons.ToLookup(p => p.Id).Select(coll => coll.First());

17

Aşağıdaki kod işlevsel olarak Jon Skeet'in cevabına eşdeğerdir .

.NET 4.5 üzerinde test edilmiş, önceki LINQ sürümlerinde çalışmalıdır.

public static IEnumerable<TSource> DistinctBy<TSource, TKey>(
  this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
  HashSet<TKey> seenKeys = new HashSet<TKey>();
  return source.Where(element => seenKeys.Add(keySelector(element)));
}

Kesinlikle, Jon Skeet'in Google Code'daki DistinctBy.cs'in en son sürümünü inceleyin .


3
Bu bana "dizinin değer hatası yok" verdi, ancak Skeet'in cevabı doğru sonucu verdi.
Soğuk Olacak Ne

10

Aşağıdaki gibi yapabilmeniz için Distinct işlevinin nasıl genişletileceğini açıklayan bir makale yazdım:

var people = new List<Person>();

people.Add(new Person(1, "a", "b"));
people.Add(new Person(2, "c", "d"));
people.Add(new Person(1, "a", "b"));

foreach (var person in people.Distinct(p => p.ID))
    // Do stuff with unique list here.

İşte makalesi: LINQ genişletme - Farklı işlevinde bir özellik belirtme


3
Makalenizde bir hata var, Distinct'ten sonra bir <T> olmalı: public static IEnumerable <T> Distinct (this ... Ayrıca (güzel olarak) bir özellik üzerinde çalışacak gibi görünmüyor yani ve soyadı
row1

2
+1, küçük bir hata downvote için yeterli bir neden değildir, bu kadar aptalca bir yazım hatası sık sık çağırır. Ve henüz herhangi bir sayıda mülk için çalışacak genel bir fonksiyon göremiyorum! Umarım downvoter bu konudaki diğer cevapları da düşürmüştür. Ama hey bu ikinci tip nesne nedir ?? İtiraz ediyorum !
nawfal

4
Bağlantınız koptu
Tom Lint

7

Şahsen ben aşağıdaki sınıfı kullanıyorum:

public class LambdaEqualityComparer<TSource, TDest> : 
    IEqualityComparer<TSource>
{
    private Func<TSource, TDest> _selector;

    public LambdaEqualityComparer(Func<TSource, TDest> selector)
    {
        _selector = selector;
    }

    public bool Equals(TSource obj, TSource other)
    {
        return _selector(obj).Equals(_selector(other));
    }

    public int GetHashCode(TSource obj)
    {
        return _selector(obj).GetHashCode();
    }
}

Ardından, bir uzantı yöntemi:

public static IEnumerable<TSource> Distinct<TSource, TCompare>(
    this IEnumerable<TSource> source, Func<TSource, TCompare> selector)
{
    return source.Distinct(new LambdaEqualityComparer<TSource, TCompare>(selector));
}

Son olarak, amaçlanan kullanım:

var dates = new List<DateTime>() { /* ... */ }
var distinctYears = dates.Distinct(date => date.Year);

Bu yaklaşımı kullanarak bulduğum avantaj, LambdaEqualityComparersınıfın bir kabul eden diğer yöntemler için yeniden kullanılmasıdır IEqualityComparer. (Oh, ve ben yieldorijinal LINQ uygulamasına bırakıyorum ...)


5

Birden çok özellik için bir Distinct yöntemine ihtiyacınız varsa, PowerfulExtensions kütüphanemi kontrol edebilirsiniz . Şu anda çok genç bir aşamada, ancak zaten herhangi bir sayıdaki mülk dışında Distinct, Union, Intersect, gibi yöntemleri kullanabilirsiniz;

Bunu şu şekilde kullanırsınız:

using PowerfulExtensions.Linq;
...
var distinct = myArray.Distinct(x => x.A, x => x.B);

5

Projemizde böyle bir görevle karşılaştığımızda, karşılaştırıcılar oluşturmak için küçük bir API tanımladık.

Yani, kullanım örneği şöyleydi:

var wordComparer = KeyEqualityComparer.Null<Word>().
    ThenBy(item => item.Text).
    ThenBy(item => item.LangID);
...
source.Select(...).Distinct(wordComparer);

Ve API'nin kendisi şöyle:

using System;
using System.Collections;
using System.Collections.Generic;

public static class KeyEqualityComparer
{
    public static IEqualityComparer<T> Null<T>()
    {
        return null;
    }

    public static IEqualityComparer<T> EqualityComparerBy<T, K>(
        this IEnumerable<T> source,
        Func<T, K> keyFunc)
    {
        return new KeyEqualityComparer<T, K>(keyFunc);
    }

    public static KeyEqualityComparer<T, K> ThenBy<T, K>(
        this IEqualityComparer<T> equalityComparer,
        Func<T, K> keyFunc)
    {
        return new KeyEqualityComparer<T, K>(keyFunc, equalityComparer);
    }
}

public struct KeyEqualityComparer<T, K>: IEqualityComparer<T>
{
    public KeyEqualityComparer(
        Func<T, K> keyFunc,
        IEqualityComparer<T> equalityComparer = null)
    {
        KeyFunc = keyFunc;
        EqualityComparer = equalityComparer;
    }

    public bool Equals(T x, T y)
    {
        return ((EqualityComparer == null) || EqualityComparer.Equals(x, y)) &&
                EqualityComparer<K>.Default.Equals(KeyFunc(x), KeyFunc(y));
    }

    public int GetHashCode(T obj)
    {
        var hash = EqualityComparer<K>.Default.GetHashCode(KeyFunc(obj));

        if (EqualityComparer != null)
        {
            var hash2 = EqualityComparer.GetHashCode(obj);

            hash ^= (hash2 << 5) + hash2;
        }

        return hash;
    }

    public readonly Func<T, K> KeyFunc;
    public readonly IEqualityComparer<T> EqualityComparer;
}

Daha fazla detay sitemizde: LINQ IEqualityComparer .


5

Bir nesne özelliğine göre Farklı kayıtlar almak için DistinctBy () kullanabilirsiniz. Kullanmadan önce aşağıdaki ifadeyi eklemeniz yeterlidir:

Microsoft.Ajax.Utilities kullanarak;

ve sonra aşağıdaki gibi kullanın:

var listToReturn = responseList.DistinctBy(x => x.Index).ToList();

Burada 'Dizin' veri farklı olmasını istediğiniz özelliktir.


4

Bunu (hızlı bir şekilde yıldırım olmasa da) yapabilirsiniz:

people.Where(p => !people.Any(q => (p != q && p.Id == q.Id)));

Yani, "listede aynı kimliğe sahip başka bir kişinin olmadığı tüm kişileri seçin."

Örneğinizde bunun sadece 3. kişiyi seçeceğini unutmayın. Önceki ikisinden hangisini istediğinizi nasıl söyleyeceğinizden emin değilim.


4

Sadece DistinctByişlevselliği elde etmek için projenize MoreLinq kütüphanesini eklemek istemiyorsanız, o zaman Linq'in argümanını Distinctalan yöntemin aşırı yüklenmesini kullanarak aynı sonucu elde edebilirsiniz IEqualityComparer.

Genel bir sınıfın iki örneğinin özel karşılaştırmasını gerçekleştirmek için lambda sözdizimini kullanan genel bir özel eşitlik karşılaştırma sınıfı oluşturarak başlarsınız:

public class CustomEqualityComparer<T> : IEqualityComparer<T>
{
    Func<T, T, bool> _comparison;
    Func<T, int> _hashCodeFactory;

    public CustomEqualityComparer(Func<T, T, bool> comparison, Func<T, int> hashCodeFactory)
    {
        _comparison = comparison;
        _hashCodeFactory = hashCodeFactory;
    }

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

    public int GetHashCode(T obj)
    {
        return _hashCodeFactory(obj);
    }
}

Daha sonra ana kodunuzda şu şekilde kullanırsınız:

Func<Person, Person, bool> areEqual = (p1, p2) => int.Equals(p1.Id, p2.Id);

Func<Person, int> getHashCode = (p) => p.Id.GetHashCode();

var query = people.Distinct(new CustomEqualityComparer<Person>(areEqual, getHashCode));

İşte bu kadar! :)

Yukarıdakiler aşağıdakileri varsayar:

  • Emlak Person.Id türüint
  • peopleToplama, boş öğeler içermiyor

Koleksiyon null içeriyorsa, null olup olmadığını kontrol etmek için lambdasları yeniden yazın, örn:

Func<Person, Person, bool> areEqual = (p1, p2) => 
{
    return (p1 != null && p2 != null) ? int.Equals(p1.Id, p2.Id) : false;
};

DÜZENLE

Bu yaklaşım Vladimir Nesterovsky'nin cevabındakine benzer ancak daha basittir.

Ayrıca Joel'in cevabındaki ile benzerdir, ancak birden fazla özelliği içeren karmaşık karşılaştırma mantığına izin verir.

Nesnelerinizin sadece hiç farklılık gösterebilir Ancak, Iddaha sonra başka bir kullanıcı yapmanız gereken tüm varsayılan uygulamaları geçersiz olduğunu doğru cevap verdi GetHashCode()ve Equals()sizin de Personsınıf ve sonra sadece out-of-the-box kullanmak Distinct()filtreye Linq yöntemini kopyaları dışarı.


Ben dikte sadece benzersiz öğeleri almak istiyorum, yardımcı olabilir misiniz, ben TempDT isNot Nothing sonra m_ConcurrentScriptDictionary = TempDT.AsEnumerable.ToDictionary (İşlev (x) x.SafeField (fldClusterId, NULL_ID_VALUE), İşlev (y) y.SafeField (fldParamValue11, NULL_ID_VALUE))
RSB


1
List<Person>lst=new List<Person>
        var result1 = lst.OrderByDescending(a => a.ID).Select(a =>new Player {ID=a.ID,Name=a.Name} ).Distinct();

Eğer istediniz Select() new Personyerine new Player? Sipariş verdiğiniz bir IDşekilde Distinct(), bu özelliği benzersizliği belirlerken kullanmayı bir şekilde bilgilendirmez , bu yüzden bu işe yaramaz.
BACON

1

Eşittir (object obj) ve GetHashCode () yöntemlerini geçersiz kıl :

class Person
{
    public int Id { get; set; }
    public int Name { get; set; }

    public override bool Equals(object obj)
    {
        return ((Person)obj).Id == Id;
        // or: 
        // var o = (Person)obj;
        // return o.Id == Id && o.Name == Name;
    }
    public override int GetHashCode()
    {
        return Id.GetHashCode();
    }
}

ve daha sonra arayın:

List<Person> distinctList = new[] { person1, person2, person3 }.Distinct().ToList();

Ancak GetHashCode () daha ileri olmalıdır (Ad da saymak için), bu cevap muhtemelen bence en iyisidir. Aslında, hedef mantığı arşivlemek için GetHashCode () 'u geçersiz kılmaya gerek yoktur, Equals () yeterlidir, ancak performansa ihtiyacımız varsa bunu geçersiz kılmalıyız. Tüm karşılaştırma günlükleri, önce karmayı kontrol edin ve eşitlerse Equals () öğesini çağırın.
Oleg Skripnyak

Ayrıca, Equals () 'da ilk satır "if (! (Obj is Person)) false döndürürse" olmalıdır. Ancak en iyi uygulama, "var o = obj as Person; eğer (o == null) false döndürürse;" gibi bir türe dökülen ayrı bir nesne kullanmaktır. sonra döküm olmadan o ile eşitliği kontrol edin
Oleg Skripnyak

1
Bunun gibi Eşitlikleri geçersiz kılmak iyi bir fikir değildir, çünkü Kişinin Eşitliğinin tek bir mülkten daha fazlasına karar vermesini bekleyen diğer programcılar için istenmeyen sonuçları olabilir.
B2K

0

Person.id'de Eşittir yapmak için Kişi üzerinde Eşitlikleri geçersiz kılabilmelisiniz. Bunun peşinde olduğunuz davranışla sonuçlanması gerekir.


-5

Lütfen aşağıdaki kodu deneyin.

var Item = GetAll().GroupBy(x => x .Id).ToList();

3
Kısa bir cevap açığız, ancak sorunun arkasında neler olduğunu anlamaya çalışan ikinci kullanıcılara çok fazla değer sağlamayacaktır. Lütfen soruna neden olacak gerçek sorunun ne olduğunu ve nasıl çözüleceğini açıklamak için biraz zaman ayırın. Teşekkür ederim ~
Hearen
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.