2010-03-29 19 views
97

Ich habe eine Sammlung:Distinct durch Eigenschaft der Klasse mit LINQ

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

Autos CarCode durch ihre Eigenschaft eindeutig identifiziert werden.

Ich habe drei Autos in der Sammlung, und zwei mit identischen CarCodes.

Wie kann ich LINQ verwenden, um diese Sammlung in Autos mit einzigartigen CarCodes zu konvertieren?

Antwort

183

Sie Gruppierung verwenden können, und bekommen das erste Auto von jedem Gruppe:

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

@NateGates: Ich habe gerade mit der Person gesprochen, die vor zwei Tagen den Kurs abgelehnt hat. – Guffa

+0

Ich denke, dass kein Overhead existiert! –

+5

@AmirHosseinMehrvarzi: Es gibt ein wenig Overhead, da die Gruppen erstellt werden, und dann nur ein Element aus jeder Gruppe verwendet wird. – Guffa

90

Verwendung MoreLINQ, die eine DistinctBy Methode hat :)

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

(Dies ist nur für LINQ to Objects, wohlgemerkt.)

+3

nur mit dem Link http: //code.google.com/p/morelinq/source/browse/MoreLinq/? R = d4396b9ff63932be0ab07c36452a481d20f96307 – Diogo

+1

Hallo Jon, zwei Fragen, wenn ich darf. 1) Warum fügst du die Bibliothek nicht zu Nuget hinzu? 2) Was ist mit LINQ zu SQL \ EF \ NH? Wie können wir das umsetzen? Müssen wir die Guffa-Version verwenden (was ist Ihre Version, wenn 'NO_HASHSET' wahr ist ...)? Vielen Dank! – gdoron

+2

@gdoron: 1) Es ist bereits in NuGet: http://www.nuget.org/packages/morelinq 2) Ich bezweifle, dass LINQ zu SQL usw. flexibel genug sind, das zu erlauben. –

25

Sie können einen IEqualityComparer implementieren und diesen in Ihrer Distinct-Erweiterung verwenden.

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 
} 

Und dann

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

Wie können wir das ohne zu schreiben verwenden: new CarEqualityComparer()? – Parsa

1

Ein anderer Weg, um das Gleiche zu erreichen ...

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

Es ist möglich, eine Verlängerung Methode, dies zu tun in einer allgemeinere Art und Weise zu erstellen. Es wäre interessant, wenn jemand die Leistung dieses "DistinctBy" gegen den GroupBy-Ansatz evaluieren könnte.

+0

Das zweite 'Select' wäre eine O (n * m) -Operation, die nicht gut skaliert werden kann. Es könnte besser funktionieren, wenn es viele Duplikate gibt, d. H. Wenn das Ergebnis der ersten Auswahl ein sehr kleiner Teil der ursprünglichen Sammlung ist. – Guffa

30

gleichen Ansatz wie Guffa sondern als Erweiterung Methode:

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

als:

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

Sie können meine PowerfulExtensions Bibliothek auszuchecken. Derzeit ist es in einem sehr jungen Stadium, aber Sie können bereits Methoden wie Distinct, Union, Intersect, außer einer beliebigen Anzahl von Eigenschaften verwenden;

Dies ist, wie Sie es verwenden:

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

Wenn ich eine Liste von Objekten habe, in denen ich alle Objekte mit den gleichen IDs löschen möchte, ist das 'myList.Distinct (x => x.ID)'? – Thomas

3

Eine andere Erweiterung Methode für Linq-to-Objekte, ohne GroupBy mit:

/// <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

Ich denke, die beste Möglichkeit, die Leistung in Bedingungen (oder In jeder Hinsicht) ist zu unterscheiden mit der IEqualityComparer Schnittstelle.

Obwohl jedesmal ein neuer Vergleich für jede Klasse implementiert wird, ist dies umständlich und führt zu Codebausteinen.

Also hier ist eine Erweiterung Methode, die eine neue IEqualityComparer im laufenden Betrieb für jede Klasse mit Reflexion erzeugt.

Verbrauch:

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

Erweiterungsmethode-Code

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

Wo ist die Reflexion hier? – MistyK

0

Sie können effektiv nicht Distinct auf einer Sammlung von Objekten (ohne zusätzliche Arbeit) verwenden. Ich werde erklären warum.

The documentation says:

Es nutzt den Standardgleichheitsvergleich, Default, Werte zu vergleichen.

Bei Objekten bedeutet dies, dass zum Vergleich der Objekte die Standardmethode equation verwendet wird (source). Das ist auf ihrem Hash-Code. Und da Ihre Objekte die GetHashCode() und Equals Methoden nicht implementieren, wird es auf die Referenz des Objekts überprüfen, die nicht eindeutig sind.

Verwandte Themen