Farklı () lambda ile?


746

Doğru, bu yüzden bir numaralandırılabilirim ve ondan farklı değerler almak istiyorum.

Kullanarak System.Linq, elbette denilen bir uzantı yöntemi var Distinct. Basit durumda, aşağıdaki gibi hiçbir parametre olmadan kullanılabilir:

var distinctValues = myStringList.Distinct();

İyi ve iyi, ama eşitliğini belirtmem gereken sayısız nesne varsa, mevcut tek aşırı yük:

var distinctValues = myCustomerList.Distinct(someEqualityComparer);

Eşitlik karşılaştırma argümanı bir örnek olmalıdır IEqualityComparer<T>. Bunu elbette yapabilirim, ama biraz ayrıntılı ve cludgy.

Beklediğim bir lambda alacak bir aşırı yük olduğunu, bir Func <T, T, bool> deyin:

var distinctValues
    = myCustomerList.Distinct((c1, c2) => c1.CustomerId == c2.CustomerId);

Herkes böyle bir uzantı var mı, ya da eşdeğer bir geçici çözüm var mı? Yoksa bir şey mi kaçırıyorum?

Alternatif olarak, bir IEqualityComparer satır içi belirtmenin bir yolu var mı (beni uyandır)?

Güncelleme

Bir Anders Hejlsberg tarafından bir cevap buldum yazı bu konuda bir MSDN forumu. Diyor:

Karşılaşacağınız sorun, iki nesne eşit olduğunda, aynı GetHashCode dönüş değerine sahip olmalarıdır (veya başka bir deyişle, Distinct tarafından dahili olarak kullanılan karma tablosu düzgün çalışmaz). IEqualityComparer kullanıyoruz, çünkü Equals ve GetHashCode uyumlu uygulamaları tek bir arayüzde paketliyor.

Sanırım bu mantıklı ..


2
bkz stackoverflow.com/questions/1183403/... GroupBy kullanarak bir çözüm için

17
Anders Hejlsberg güncellemesi için teşekkürler!
Tor Haugen

Hayır, mantıklı değil - aynı değerleri içeren iki nesne nasıl iki farklı karma kod döndürür?
GY

Bu yardımcı olabilir - çözüm için .Distinct(new KeyEqualityComparer<Customer,string>(c1 => c1.CustomerId)), ve GetHashCode () düzgün çalışması için neden önemli olduğunu açıklar.
marbel82

Yanıtlar:


1028
IEnumerable<Customer> filteredList = originalList
  .GroupBy(customer => customer.CustomerId)
  .Select(group => group.First());

12
Mükemmel! Bunun, bir uzantı yönteminde de kapsüllenmesi gerçekten kolaydır, DistinctBy(hatta Distinctimza benzersiz olacağından).
Tomas Aschan

1
Benim için çalışmıyor! <'First' yöntemi yalnızca son sorgu işlemi olarak kullanılabilir. Bunun yerine 'FirstOrDefault' yöntemini kullanmayı düşünün.> Ben bile 'FirstOrDefault' denedim, işe yaramadı.
JatSing

63
@TorHaugen: Tüm bu grupları oluşturmanın bir maliyeti olduğunu unutmayın. Bu, girişi aktaramaz ve hiçbir şey döndürmeden önce tüm verileri arabelleğe alır. Bu durumla ilgili olmayabilir, ancak DistinctBy'nin zarafetini tercih ediyorum :)
Jon Skeet

2
@JonSkeet: Bu, yalnızca bir özellik için ek kitaplıklar almak istemeyen VB.NET kodlayıcıları için yeterince iyidir. ASync CTP olmadan VB.NET yieldifadeyi desteklemediğinden akış teknik olarak mümkün değildir. Yine de cevabınız için teşekkürler. C # 'da kodlarken kullanacağım. ;-)
Alex Essilfie

2
@BenGripka: Bu tamamen aynı değil. Size yalnızca müşteri kimliklerini verir. Tüm müşteriyi istiyorum :)
ryanman

496

İstediğiniz gibi bana bakıyor DistinctBydan MoreLINQ . Daha sonra şunları yazabilirsiniz:

var distinctValues = myCustomerList.DistinctBy(c => c.CustomerId);

İşte bir kısaltılmış sürümü DistinctBy(iptal kontrolü yok ve kendi anahtar karşılaştırıcınızı belirtme seçeneği yok):

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

14
En iyi cevabın sadece yazı başlığını okuyarak Jon Skeet tarafından yayınlanacağını biliyordum. Eğer LINQ ile bir ilgisi varsa, Skeet senin erkeğin. Tanrı benzeri linq bilgisine ulaşmak için 'C # Derinlik' bölümünü okuyun.
nocarrier

2
mükemmel cevap!!! Ayrıca, yield+ ekstra lib hakkında tüm VB_Complainers için , foreach yeniden yazılabilirreturn source.Where(element => knownKeys.Add(keySelector(element)));
denis morozov

5
@ sudhAnsu63 bu LinqToSql (ve diğer linq sağlayıcıları) için bir sınırlamadır. LinqToX'ın amacı, C # lambda ifadenizi X'in yerel bağlamına çevirmektir. Yani, LinqToSql, C #'ınızı SQL'e dönüştürür ve bu komutu mümkün olan her yerde doğal olarak yürütür. Bu, SQL'de ifade etmenin bir yolu yoksa (veya kullandığınız linq sağlayıcısı) C # 'da bulunan herhangi bir yöntemin bir linqProvider'dan "geçirilemeyeceği" anlamına gelir. Veri nesnelerini modelleri görüntülemek için dönüştürmek için uzantı yöntemlerinde bunu görüyorum. Sorguyu "gerçekleştirerek" DistinctBy () 'den önce ToList () çağırarak bu sorunu çözebilirsiniz.
Michael Blackburn

1
Ve bu soruya her geldiğimde, MoreLinq'in en azından bir kısmını BCL'ye neden kabul etmediklerini merak etmeye devam ediyorum.
Shimmy Weitzhandler

2
@Shimmy: Kesinlikle memnuniyetle karşılarım ... Fizibilitenin ne olduğundan emin değilim. Yine de .NET Vakfı'nda yükseltebilirim ...
Jon Skeet

39

Bir şeyleri sarmak için . Buraya benim gibi gelen insanların çoğunun, herhangi bir kütüphane kullanmadan ve mümkün olan en iyi performansla mümkün olan en basit çözümü istediğini düşünüyorum .

(Benim için yöntemle kabul edilen grup, performans açısından aşırıya kaçma olduğunu düşünüyorum.)

İşte null değerleri için de çalışan IEqualityComparer arabirimini kullanan basit bir uzantı yöntemi .

Kullanımı:

var filtered = taskList.DistinctBy(t => t.TaskExternalId).ToArray();

Uzantı Yöntemi Kodu

public static class LinqExtensions
{
    public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> items, Func<T, TKey> property)
    {
        GeneralPropertyComparer<T, TKey> comparer = new GeneralPropertyComparer<T,TKey>(property);
        return items.Distinct(comparer);
    }   
}
public class GeneralPropertyComparer<T,TKey> : IEqualityComparer<T>
{
    private Func<T, TKey> expr { get; set; }
    public GeneralPropertyComparer (Func<T, TKey> expr)
    {
        this.expr = expr;
    }
    public bool Equals(T left, T right)
    {
        var leftProp = expr.Invoke(left);
        var rightProp = expr.Invoke(right);
        if (leftProp == null && rightProp == null)
            return true;
        else if (leftProp == null ^ rightProp == null)
            return false;
        else
            return leftProp.Equals(rightProp);
    }
    public int GetHashCode(T obj)
    {
        var prop = expr.Invoke(obj);
        return (prop==null)? 0:prop.GetHashCode();
    }
}

19

Hayır bunun için böyle bir uzatma yöntemi aşırı yüklemesi yoktur. Geçmişte kendimi bu sinir bozucu buldum ve bu nedenle genellikle bu sorunla başa çıkmak için yardımcı bir sınıf yazıyorum. Hedefi dönüştürmek olduğunu Func<T,T,bool>için IEqualityComparer<T,T>.

Misal

public class EqualityFactory {
  private sealed class Impl<T> : IEqualityComparer<T,T> {
    private Func<T,T,bool> m_del;
    private IEqualityComparer<T> m_comp;
    public Impl(Func<T,T,bool> del) { 
      m_del = del;
      m_comp = EqualityComparer<T>.Default;
    }
    public bool Equals(T left, T right) {
      return m_del(left, right);
    } 
    public int GetHashCode(T value) {
      return m_comp.GetHashCode(value);
    }
  }
  public static IEqualityComparer<T,T> Create<T>(Func<T,T,bool> del) {
    return new Impl<T>(del);
  }
}

Bu, aşağıdakileri yazmanıza olanak tanır

var distinctValues = myCustomerList
  .Distinct(EqualityFactory.Create((c1, c2) => c1.CustomerId == c2.CustomerId));

8
Gerçi kötü bir karma kod uygulaması vardır. IEqualityComparer<T>Bir projeksiyondan bir oluşturmak daha kolaydır : stackoverflow.com/questions/188120/…
Jon Skeet

7
(Sadece karma kod hakkındaki yorumumu açıklamak için - bu kodla Eşittir (x, y) == true, ancak GetHashCode (x)! = GetHashCode (y) ile bitirmek çok kolaydır. .)
Jon Skeet

Karma kod itirazına katılıyorum. Yine de, desen için +1.
Tor Haugen

@Jon, evet GetHashcode orijinal uygulama optimal (tembel olmaktan az) katılıyorum. Ben şimdi biraz daha standart olan şimdi EqualityComparer <T> .Default.GetHashcode () kullanmak için değiştirdi. Gerçekten de, bu senaryoda GetHashcode uygulamasını çalıştırmak için garantili tek sabit bir değer döndürmektir. Karma aramayı öldürür, ancak işlevsel olarak doğru olduğu garanti edilir.
JaredPar

1
@JaredPar: Kesinlikle. Karma kod kullandığınız eşitlik işleviyle tutarlı olmalıdır, ki bu muhtemelen varsayılan değildir , aksi halde rahatsız etmezsiniz :) Bu yüzden bir projeksiyon kullanmayı tercih ederim - hem eşitliği hem de mantıklı bir karma elde edebilirsiniz bu şekilde kodlayın. Ayrıca, arama kodunun daha az çoğaltmaya sahip olmasını sağlar. Kuşkusuz, sadece aynı projeksiyonu iki kez istediğiniz durumlarda çalışır, ancak uygulamada gördüğüm her durum :)
Jon Skeet

18

Steno çözümü

myCustomerList.GroupBy(c => c.CustomerId, (key, c) => c.FirstOrDefault());

1
Bunun neden iyileştirildiğine dair bir açıklama ekleyebilir misiniz?
Keith Pinson

Konrad'ın yapmadığı zaman bu aslında benim için güzel çalıştı.
neoscribe

13

Bu ne istersen yapacak ama performans hakkında bilmiyorum:

var distinctValues =
    from cust in myCustomerList
    group cust by cust.CustomerId
    into gcust
    select gcust.First();

En azından ayrıntılı değil.


12

İşte ihtiyacım olan basit bir uzantı yöntemi ...

public static class EnumerableExtensions
{
    public static IEnumerable<TKey> Distinct<T, TKey>(this IEnumerable<T> source, Func<T, TKey> selector)
    {
        return source.GroupBy(selector).Select(x => x.Key);
    }
}

Bu şekilde farklı bir yöntem yapmadıkları utanç verici, ama hey ho.


bu kitaplığı morelinq eklemek zorunda kalmadan en iyi çözümdür.
toddmo

Ama değiştirmek zorunda x.Keyiçin x.First()ve dönüş değerini değiştirmekIEnumerable<T>
toddmo

@toddmo Geri bildiriminiz için teşekkürler :-) Evet, mantıklı geliyor ... Daha fazla araştırdıktan sonra cevabı güncelleyeceğim.
David Kirkland

1
basit ve temiz çözüm için teşekkür etmek için asla geç değildir
Ali

4

Kullandığım ve benim için iyi işleyen bir şey.

/// <summary>
/// A class to wrap the IEqualityComparer interface into matching functions for simple implementation
/// </summary>
/// <typeparam name="T">The type of object to be compared</typeparam>
public class MyIEqualityComparer<T> : IEqualityComparer<T>
{
    /// <summary>
    /// Create a new comparer based on the given Equals and GetHashCode methods
    /// </summary>
    /// <param name="equals">The method to compute equals of two T instances</param>
    /// <param name="getHashCode">The method to compute a hashcode for a T instance</param>
    public MyIEqualityComparer(Func<T, T, bool> equals, Func<T, int> getHashCode)
    {
        if (equals == null)
            throw new ArgumentNullException("equals", "Equals parameter is required for all MyIEqualityComparer instances");
        EqualsMethod = equals;
        GetHashCodeMethod = getHashCode;
    }
    /// <summary>
    /// Gets the method used to compute equals
    /// </summary>
    public Func<T, T, bool> EqualsMethod { get; private set; }
    /// <summary>
    /// Gets the method used to compute a hash code
    /// </summary>
    public Func<T, int> GetHashCodeMethod { get; private set; }

    bool IEqualityComparer<T>.Equals(T x, T y)
    {
        return EqualsMethod(x, y);
    }

    int IEqualityComparer<T>.GetHashCode(T obj)
    {
        if (GetHashCodeMethod == null)
            return obj.GetHashCode();
        return GetHashCodeMethod(obj);
    }
}

@Mukus Burada neden sınıf adını sorduğunuzdan emin değilim. Ben sadece benim öneki böylece IEqualityComparer uygulamak için sınıf bir şey adlandırmak gerekiyordu.
Kleinux

4

Burada gördüğüm tüm çözümler zaten karşılaştırılabilir bir alan seçmeye dayanıyor. Bununla birlikte, farklı bir şekilde karşılaştırmak gerekirse, buradaki çözüm genel olarak işe yarar gibi görünüyor:

somedoubles.Distinct(new LambdaComparer<double>((x, y) => Math.Abs(x - y) < double.Epsilon)).Count()

LambdaComparer nedir, nereden ithal ediyorsunuz?
Patrick Graham

@PatrickGraham cevapta bağlantılı: brendan.enrick.com/post/…
Dmitry Ledentsov

3

Başka bir yol alın:

var distinctValues = myCustomerList.
Select(x => x._myCaustomerProperty).Distinct();

Dizi dönüşü ayrı öğeleri bunları '_myCaustomerProperty' özelliğiyle karşılaştırır.


1
Bunu söylemek için buraya geldim. BU kabul edilen cevap olmalıdır
Still.

5
Hayır, istediğiniz tek şey özel mülkün ayrı değerleri olmadığı sürece, bu kabul edilen cevap olmamalıdır. Genel OP sorusu, nesnenin belirli bir özelliğine dayalı olarak farklı nesnelerin nasıl döndürüleceğiydi .
tomo

2

InlineComparer'ı kullanabilirsiniz

public class InlineComparer<T> : IEqualityComparer<T>
{
    //private readonly Func<T, T, bool> equalsMethod;
    //private readonly Func<T, int> getHashCodeMethod;
    public Func<T, T, bool> EqualsMethod { get; private set; }
    public Func<T, int> GetHashCodeMethod { get; private set; }

    public InlineComparer(Func<T, T, bool> equals, Func<T, int> hashCode)
    {
        if (equals == null) throw new ArgumentNullException("equals", "Equals parameter is required for all InlineComparer instances");
        EqualsMethod = equals;
        GetHashCodeMethod = hashCode;
    }

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

    public int GetHashCode(T obj)
    {
        if (GetHashCodeMethod == null) return obj.GetHashCode();
        return GetHashCodeMethod(obj);
    }
}

Kullanım örneği :

  var comparer = new InlineComparer<DetalleLog>((i1, i2) => i1.PeticionEV == i2.PeticionEV && i1.Etiqueta == i2.Etiqueta, i => i.PeticionEV.GetHashCode() + i.Etiqueta.GetHashCode());
  var peticionesEV = listaLogs.Distinct(comparer).ToList();
  Assert.IsNotNull(peticionesEV);
  Assert.AreNotEqual(0, peticionesEV.Count);

Kaynak: https://stackoverflow.com/a/5969691/206730
IEqualityComparer for Union kullanma
Açık tip karşılaştırıcımı satır içi olarak belirleyebilir miyim?


2

LambdaEqualityComparer'ı kullanabilirsiniz:

var distinctValues
    = myCustomerList.Distinct(new LambdaEqualityComparer<OurType>((c1, c2) => c1.CustomerId == c2.CustomerId));


public class LambdaEqualityComparer<T> : IEqualityComparer<T>
    {
        public LambdaEqualityComparer(Func<T, T, bool> equalsFunction)
        {
            _equalsFunction = equalsFunction;
        }

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

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

        private readonly Func<T, T, bool> _equalsFunction;
    }

1

Bunu yapmanın zor bir yolu Aggregate(), anahtar özellik değerlerini anahtar olarak akümülatör olarak bir sözlük kullanarak uzantı kullanmaktır :

var customers = new List<Customer>();

var distincts = customers.Aggregate(new Dictionary<int, Customer>(), 
                                    (d, e) => { d[e.CustomerId] = e; return d; },
                                    d => d.Values);

Ve bir GroupBy tarzı çözüm kullanıyor ToLookup():

var distincts = customers.ToLookup(c => c.CustomerId).Select(g => g.First());

Güzel, ama neden sadece bir tane Dictionary<int, Customer>yaratmıyorsun?
18'de

0

Ben bir IEnumerable var varsayalım ve örnek temsilcinizde, c1 ve c2 bu listedeki iki öğeye başvurmak istiyor musunuz?

Bunu kendi kendine katılma ile başarabileceğinize inanıyorum var differenttResults = myList'teki c1'den myList'teki c2'ye


0

Eğer Distinct()benzersiz sonuçlar üretmez, bu bir deneyin:

var filteredWC = tblWorkCenter.GroupBy(cc => cc.WCID_I).Select(grp => grp.First()).Select(cc => new Model.WorkCenter { WCID = cc.WCID_I }).OrderBy(cc => cc.WCID); 

ObservableCollection<Model.WorkCenter> WorkCenter = new ObservableCollection<Model.WorkCenter>(filteredWC);


0

Bunu nasıl yapabileceğiniz aşağıda açıklanmıştır:

public static class Extensions
{
    public static IEnumerable<T> MyDistinct<T, V>(this IEnumerable<T> query,
                                                    Func<T, V> f, 
                                                    Func<IGrouping<V,T>,T> h=null)
    {
        if (h==null) h=(x => x.First());
        return query.GroupBy(f).Select(h);
    }
}

Bu yöntem, böyle bir parametre belirterek kullanmanıza izin verir .MyDistinct(d => d.Name), ancak aynı şekilde ikinci bir parametre olarak sahip olma koşulunu belirtmenize izin verir:

var myQuery = (from x in _myObject select x).MyDistinct(d => d.Name,
        x => x.FirstOrDefault(y=>y.Name.Contains("1") || y.Name.Contains("2"))
        );

Not Bu, örneğin .LastOrDefault(...)gibi diğer işlevleri de belirtmenize olanak tanır .


Sadece koşulu ortaya çıkarmak istiyorsanız, aşağıdaki gibi uygulayarak daha da basitleştirebilirsiniz:

public static IEnumerable<T> MyDistinct2<T, V>(this IEnumerable<T> query,
                                                Func<T, V> f,
                                                Func<T,bool> h=null
                                                )
{
    if (h == null) h = (y => true);
    return query.GroupBy(f).Select(x=>x.FirstOrDefault(h));
}

Bu durumda, sorgu sadece şöyle görünecektir:

var myQuery2 = (from x in _myObject select x).MyDistinct2(d => d.Name,
                    y => y.Name.Contains("1") || y.Name.Contains("2")
                    );

NB Buradaki tanım daha basit olmakla birlikte not .MyDistinct2kullanır .FirstOrDefault(...)örtük.


Not: Yukarıdaki örnekler aşağıdaki demo sınıfını kullanmaktadır

class MyObject
{
    public string Name;
    public string Code;
}

private MyObject[] _myObject = {
    new MyObject() { Name = "Test1", Code = "T"},
    new MyObject() { Name = "Test2", Code = "Q"},
    new MyObject() { Name = "Test2", Code = "T"},
    new MyObject() { Name = "Test5", Code = "Q"}
};

0

IEnumerable lambda uzantısı:

public static class ListExtensions
{        
    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> list, Func<T, int> hashCode)
    {
        Dictionary<int, T> hashCodeDic = new Dictionary<int, T>();

        list.ToList().ForEach(t => 
            {   
                var key = hashCode(t);
                if (!hashCodeDic.ContainsKey(key))
                    hashCodeDic.Add(key, t);
            });

        return hashCodeDic.Select(kvp => kvp.Value);
    }
}

Kullanımı:

class Employee
{
    public string Name { get; set; }
    public int EmployeeID { get; set; }
}

//Add 5 employees to List
List<Employee> lst = new List<Employee>();

Employee e = new Employee { Name = "Shantanu", EmployeeID = 123456 };
lst.Add(e);
lst.Add(e);

Employee e1 = new Employee { Name = "Adam Warren", EmployeeID = 823456 };
lst.Add(e1);
//Add a space in the Name
Employee e2 = new Employee { Name = "Adam  Warren", EmployeeID = 823456 };
lst.Add(e2);
//Name is different case
Employee e3 = new Employee { Name = "adam warren", EmployeeID = 823456 };
lst.Add(e3);            

//Distinct (without IEqalityComparer<T>) - Returns 4 employees
var lstDistinct1 = lst.Distinct();

//Lambda Extension - Return 2 employees
var lstDistinct = lst.Distinct(employee => employee.EmployeeID.GetHashCode() ^ employee.Name.ToUpper().Replace(" ", "").GetHashCode()); 
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.