Distinct () mit Lambda?

Richtig, also habe ich ein Aufzählungszeichen und möchte bestimmte Werte daraus ableiten.

Mit System.Linq gibt es natürlich eine Erweiterungsmethode namens Distinct . Im einfachen Fall kann es ohne Parameter verwendet werden, wie:

 var distinctValues = myStringList.Distinct(); 

Gut und gut, aber wenn ich eine Aufzählung von Objekten habe, für die ich Gleichheit angeben muss, ist die einzige verfügbare Überladung:

 var distinctValues = myCustomerList.Distinct(someEqualityComparer); 

Das Gleichheitsvergleichsargument muss eine Instanz von IEqualityComparer . Ich kann das natürlich, aber es ist etwas ausführlich und, naja, Clodgy.

Was ich erwartet hätte, ist eine Überlastung, die ein Lambda, sagen wir ein Func nehmen würde:

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

Wer weiß, ob es eine solche Erweiterung oder eine gleichwertige Problemumgehung gibt? Oder fehlt mir etwas?

Oder gibt es eine Möglichkeit, einen IEqualityComparer inline (embaras me) anzugeben?

Aktualisieren

Ich habe eine Antwort von Anders Hejlsberg auf einen Beitrag in einem MSDN-Forum zu diesem Thema gefunden. Er sagt:

Das Problem, mit dem Sie konfrontiert werden, ist, dass, wenn zwei Objekte gleich sind, sie den gleichen GetHashCode-Rückgabewert haben müssen (sonst wird die von Distinct intern verwendete Hash-Tabelle nicht korrekt funktionieren). Wir verwenden IEqualityComparer, weil es kompatible Implementierungen von Equals und GetHashCode in eine einzige Schnittstelle packt.

Ich denke, das macht Sinn.

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

Es sieht für mich so aus, als wollten Sie DistinctBy von MoreLINQ . Sie können dann schreiben:

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

Hier ist eine abgespeckte Version von DistinctBy (keine Nichtigkeitsüberprüfung und keine Option, um Ihren eigenen Schlüsselvergleich zu spezifizieren):

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

Um die Dinge einpacken . Ich denke, die meisten Leute, die wie ich hierher gekommen sind, wollen die einfachste Lösung ohne Bibliotheken und mit bestmöglicher performance .

(Die akzeptierte Gruppe von Methode für mich denke ich ist ein Overkill in Bezug auf die performance.)

Hier ist eine einfache Erweiterungsmethode, die die IEqualityComparer- Schnittstelle verwendet, die auch für Nullwerte funktioniert.

Verwendung:

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

Erweiterungsmethode

 public static class LinqExtensions { public static IEnumerable DistinctBy(this IEnumerable items, Func property) { GeneralPropertyComparer comparer = new GeneralPropertyComparer(property); return items.Distinct(comparer); } } public class GeneralPropertyComparer : IEqualityComparer { private Func expr { get; set; } public GeneralPropertyComparer (Func 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(); } } 

Nein, dafür gibt es keine Überladung der Erweiterungsmethode. Ich habe das selbst in der Vergangenheit als frustrierend empfunden und schreibe normalerweise eine Hilfsklasse, um mit diesem Problem umzugehen. Ziel ist es, einen Func in IEqualityComparer .

Beispiel

 public class EqualityFactory { private sealed class Impl : IEqualityComparer { private Func m_del; private IEqualityComparer m_comp; public Impl(Func del) { m_del = del; m_comp = EqualityComparer.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 Create(Func del) { return new Impl(del); } } 

So können Sie Folgendes schreiben

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

Kurzschrift-Lösung

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

Dies wird tun, was Sie wollen, aber ich weiß nichts über die performance:

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

Zumindest ist es nicht ausführlich.

Hier ist eine einfache Erweiterungsmethode, die das tut, was ich brauche …

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

Es ist eine Schande, dass sie nicht eine bestimmte Methode wie diese in den Rahmen gebrannt haben, aber hey ho.

Etwas, das ich benutzt habe, was gut für mich funktioniert hat.

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

Alle Lösungen, die ich hier gesehen habe, sind darauf angewiesen, ein bereits vergleichbares Feld auszuwählen. Wenn man jedoch auf eine andere Weise vergleichen möchte, scheint diese Lösung hier allgemein zu funktionieren, für etwas wie:

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

Sie können InlineComparer verwenden

 public class InlineComparer : IEqualityComparer { //private readonly Func equalsMethod; //private readonly Func getHashCodeMethod; public Func EqualsMethod { get; private set; } public Func GetHashCodeMethod { get; private set; } public InlineComparer(Func equals, Func 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); } } 

Verwendungsbeispiel :

  var comparer = new InlineComparer((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); 

Quelle: https://stackoverflow.com/a/5969691/206730
IEqualityComparer für Union verwenden
Kann ich meinen expliziten Typvergleicher inline angeben?

Eine knifflige Methode hierfür ist die Verwendung der Aggregate() -Erweiterung, die ein Wörterbuch als Akkumulator mit den key-property- Werten als Schlüssel verwendet:

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

Und eine GroupBy-ähnliche Lösung verwendet ToLookup() :

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

Nimm einen anderen Weg:

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

Die Sequenz gibt verschiedene Elemente zurück und vergleicht sie mit der Eigenschaft ‘_myCa- mustorProperty’.

Ich nehme an, Sie haben ein IEnumerable, und in Ihrem Beispiel Delegat möchten Sie c1 und c2 auf zwei Elemente in dieser Liste verweisen?

Ich glaube, Sie könnten dies mit einem Self-Join erreichen var distinctResults = von c1 in myList Join c2 in myList on

Wenn Distinct() keine eindeutigen Ergebnisse liefert, versuchen Sie Folgendes:

 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 WorkCenter = new ObservableCollection(filteredWC); 

Das Microsoft System.Interactive-Paket verfügt über eine Version von Distinct, die ein Schlüsselselektor Lambda übernimmt. Das ist praktisch dasselbe wie die Lösung von Jon Skeet, aber es kann hilfreich sein, um den Rest der Bibliothek zu kennen und zu überprüfen.

Hier ist, wie Sie es tun können:

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

Mit dieser Methode können Sie einen Parameter wie .MyDistinct(d => d.Name) , aber Sie können auch eine .MyDistinct(d => d.Name) Bedingung als zweiten Parameter angeben:

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

.LastOrDefault(...) Sie können damit auch andere functionen wie zum Beispiel .LastOrDefault(...) angeben.


Wenn Sie nur die Bedingung aufdecken möchten, können Sie es noch einfacher machen, indem Sie es wie folgt implementieren:

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

In diesem Fall würde die Abfrage wie folgt aussehen:

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

NB Hier ist der Ausdruck einfacher, aber beachten Sie. .MyDistinct2 verwendet .FirstOrDefault(...) implizit.


Hinweis: In den obigen Beispielen wird die folgende Demoklasse verwendet

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

IEnumerable Lambda-Erweiterung:

 public static class ListExtensions { public static IEnumerable Distinct(this IEnumerable list, Func hashCode) { Dictionary hashCodeDic = new Dictionary(); list.ToList().ForEach(t => { var key = hashCode(t); if (!hashCodeDic.ContainsKey(key)) hashCodeDic.Add(key, t); }); return hashCodeDic.Select(kvp => kvp.Value); } } 

Verwendung:

 class Employee { public string Name { get; set; } public int EmployeeID { get; set; } } //Add 5 employees to List List lst = new List(); 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) - 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());