2010-03-29 11 views
97

Mam kolekcję:W odróżnieniu od własności klasie LINQ

List<Car> cars = new List<Car>(); 

Samochody są jednoznacznie identyfikowane przez ich własności CarCode.

Mam trzy samochody w kolekcji i dwa z identycznymi CarCodes.

Jak mogę użyć LINQ, aby przekonwertować tę kolekcję na samochody z unikalnymi CarCodes?

Odpowiedz

183

Można użyć grupowania i zdobyć pierwszy samochód od siebie grupa:

List<Car> distinct = 
    cars 
    .GroupBy(car => car.CarCode) 
    .Select(g => g.First()) 
    .ToList(); 
+0

@NateGates: Rozmawiałem z osobą, która przyjęła dwa dni temu. – Guffa

+0

Myślę, że nie istnieje nadmiar! –

+5

@AmirHosseinMehrvarzi: Trochę narzutów, ponieważ grupy są tworzone, a następnie używany jest tylko jeden element z każdej grupy. – Guffa

90

Zastosowanie MoreLINQ, który ma DistinctBy metodę :)

IEnumerable<Car> distinctCars = cars.DistinctBy(car => car.CarCode); 

(To jest tylko dla LINQ to Objects, pamiętajcie.)

+3

wystarczy podać link! Http: //code.google.com/p/morelinq/source/browse/MoreLinq/? R = d4396b9ff63932be0ab07c36452a481d20f96307 – Diogo

+1

Witam Jon, mam dwa pytania, jeśli mogę. 1) Dlaczego nie dodasz biblioteki do Nuget? 2) A co z LINQ do SQL \ EF \ NH? jak możemy to zrealizować? Czy musimy używać wersji Guffa (która jest twoją wersją, jeśli "NO_HASHSET" jest prawdziwe ...)? Dziękuję Ci bardzo! – gdoron

+2

@gdoron: 1) Jest już w NuGet: http://www.nuget.org/packages/morelinq 2) Wątpię, czy LINQ do SQL etc są wystarczająco elastyczne, aby to umożliwić. –

25

Możesz zaimplementować IEqualityComparer i użyć go w swoim Rozwińnym rozszerzeniu.

class CarEqualityComparer : IEqualityComparer<Car> 
{ 
    #region IEqualityComparer<Car> Members 

    public bool Equals(Car x, Car y) 
    { 
     return x.CarCode.Equals(y.CarCode); 
    } 

    public int GetHashCode(Car obj) 
    { 
     return obj.CarCode.GetHashCode(); 
    } 

    #endregion 
} 

A potem

var uniqueCars = cars.Distinct(new CarEqualityComparer()); 
+0

Jak możemy użyć tego bez pisania: new CarEqualityComparer()? – Parsa

1

Innym sposobem, aby osiągnąć to samo ...

List<Car> distinticBy = cars 
    .Select(car => car.CarCode) 
    .Distinct() 
    .Select(code => cars.First(car => car.CarCode == code)) 
    .ToList(); 

Jest możliwe, aby utworzyć metodę rozszerzenia, aby to zrobić w sposób bardziej ogólny. Byłoby interesujące, gdyby ktoś mógł ocenić wydajność tego "DistinctBy" w stosunku do podejścia GroupBy.

+0

Drugie "Wybierz" będzie operacją O (n * m), więc nie będzie się dobrze skalować. Mogłoby działać lepiej, gdyby było dużo duplikatów, tj. Jeśli wynik pierwszego 'Select' jest bardzo małą częścią oryginalnej kolekcji. – Guffa

30

samo podejście jak Guffa ale jako metodę rozszerzenia:

public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> items, Func<T, TKey> property) 
{ 
    return items.GroupBy(property).Select(x => x.First()); 
} 

Używany jako:

var uniqueCars = cars.DistinctBy(x => x.CarCode); 
1

Można sprawdzić moje PowerfulExtensions bibliotekę. Obecnie jest na bardzo młodym etapie, ale już możesz używać takich metod jak Distinct, Union, Intersect, Z wyjątkiem dowolnej liczby właściwości;

To jak go używać:

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

Jeśli mam listę obiektów, w których chcę usunąć wszystkie obiekty o tym samym identyfikatorze, czy będzie to "myList.Distinct (x => x.ID)"? – Thomas

3

Inną metodę rozszerzenia dla LINQ do obiektów bez użycia GroupBy:

/// <summary> 
    /// Returns the set of items, made distinct by the selected value. 
    /// </summary> 
    /// <typeparam name="TSource">The type of the source.</typeparam> 
    /// <typeparam name="TResult">The type of the result.</typeparam> 
    /// <param name="source">The source collection.</param> 
    /// <param name="selector">A function that selects a value to determine unique results.</param> 
    /// <returns>IEnumerable&lt;TSource&gt;.</returns> 
    public static IEnumerable<TSource> Distinct<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector) 
    { 
     HashSet<TResult> set = new HashSet<TResult>(); 

     foreach(var item in source) 
     { 
      var selectedValue = selector(item); 

      if (set.Add(selectedValue)) 
       yield return item; 
     } 
    } 
3

Myślę, że najlepszym rozwiązaniem pod względem wydajności (lub w dowolnych kategoriach) jest Distinct za pomocą interfejsu IEqualityComparer.

Mimo to za każdym razem, gdy nowy korektor dla każdej klasy jest uciążliwy i generuje standardowy kod.

Oto więc metoda rozszerzenia, która pozwala uzyskać nową klasę IEqualityComparer dla każdej klasy za pomocą odbicia.

Zastosowanie:

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

Extension Code Metoda

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

gdzie jest tu odbicie? – MistyK

0

nie można skutecznie wykorzystać Distinct na zbiór obiektów (bez dodatkowej pracy). Wyjaśnię dlaczego.

The documentation says:

Używa domyślnej porównywarka równość, Default i porównać wartości.

Dla obiektów oznacza to, że do porównania obiektów używa domyślnej metody równania (source). To jest na ich kodzie haszującym. A ponieważ twoje obiekty nie implementują metod GetHashCode() i Equals, sprawdzą odwołanie do obiektu, który nie jest odrębny.

Powiązane problemy