32 Stimmen

Wie erhält man eine eindeutige Instanz aus einer Liste durch Lambda oder LINQ

Ich habe eine Klasse wie diese:

class MyClass<T> {
    public string value1 { get; set; }
    public T objT { get; set; }
}

und eine Liste dieser Klasse. Ich möchte .net 3.5 Lambda oder Linq verwenden, um eine Liste von MyClass durch unterschiedliche Wert1 zu erhalten. Ich denke, dies ist möglich und viel einfacher als die Art und Weise in .net 2.0 eine Liste wie diese zu zwischenspeichern:

List<MyClass<T>> list; 
...
List<MyClass<T>> listDistinct = new List<MyClass<T>>();
foreach (MyClass<T> instance in list)
{
    // some code to check if listDistinct does contain obj with intance.Value1
    // then listDistinct.Add(instance);
}

Was ist die Lambda oder LINQ Weg, es zu tun?

68voto

jpbochi Punkte 4236

Beide Marc und dahlbyk Die Antworten der Kommission scheinen sehr gut zu funktionieren. Ich habe jedoch eine viel einfachere Lösung. Statt der Verwendung von Distinct können Sie verwenden GroupBy . Es geht folgendermaßen:

var listDistinct
    = list.GroupBy(
        i => i.value1,
        (key, group) => group.First()
    ).ToArray();

Beachten Sie, dass ich zwei Funktionen an die GroupBy() . Der erste ist ein Schlüsselselektor. Mit dem zweiten wird nur ein Element aus jeder Gruppe ausgewählt. Ihrer Frage entnehme ich, dass First() war die richtige. Sie können eine andere schreiben, wenn Sie wollen. Sie können versuchen Last() um zu sehen, was ich meine.

Ich habe einen Test mit der folgenden Eingabe durchgeführt:

var list = new [] {
    new { value1 = "ABC", objT = 0 },
    new { value1 = "ABC", objT = 1 },
    new { value1 = "123", objT = 2 },
    new { value1 = "123", objT = 3 },
    new { value1 = "FOO", objT = 4 },
    new { value1 = "BAR", objT = 5 },
    new { value1 = "BAR", objT = 6 },
    new { value1 = "BAR", objT = 7 },
    new { value1 = "UGH", objT = 8 },
};

Das Ergebnis war:

//{ value1 = ABC, objT = 0 }
//{ value1 = 123, objT = 2 }
//{ value1 = FOO, objT = 4 }
//{ value1 = BAR, objT = 5 }
//{ value1 = UGH, objT = 8 }

Ich habe sie nicht auf ihre Leistung getestet. Ich glaube, dass diese Lösung wahrscheinlich ein wenig langsamer ist als eine, die Distinct . Trotz dieses Nachteils gibt es zwei große Vorteile: Einfachheit und Flexibilität. Normalerweise ist es besser, die Einfachheit der Optimierung vorzuziehen, aber das hängt wirklich von dem Problem ab, das man zu lösen versucht.

0 Stimmen

Sehr interessant. Die Comparer-Methode hat allerdings eine Einschränkung: Sie liefert nur den Unterschied zum ersten Fund. Wenn ich die Flexibilität brauche, um die Unterscheidung durch die zweite, ..., oder die letzte, nicht sicher, ob group.xxx() wäre in der Lage, es zu tun?

0 Stimmen

Ja, das würden Sie. Einfach ersetzen First() para Last() und sehen. Natürlich können Sie auch jede andere komplexe Auswahl treffen, wenn Sie sie benötigen.

0 Stimmen

@David: Sie sollten sich überlegen, ob Sie diese Antwort akzeptieren. Sie ist eine flexible und elegante Lösung für Ihr Problem.

9voto

Marc Gravell Punkte 970173

Hmm... Ich würde wahrscheinlich eine benutzerdefinierte IEqualityComparer<T> damit ich sie benutzen kann:

var listDistinct = list.Distinct(comparer).ToList();

und schreiben Sie den Komparator über LINQ....

Vielleicht ein bisschen übertrieben, aber zumindest wiederverwendbar:

Zuerst die Verwendung:

static class Program {
    static void Main() {
        var data = new[] {
            new { Foo = 1,Bar = "a"}, new { Foo = 2,Bar = "b"}, new {Foo = 1, Bar = "c"}
        };
        foreach (var item in data.DistinctBy(x => x.Foo))
            Console.WriteLine(item.Bar);
        }
    }
}

Mit Utility-Methoden:

public static class ProjectionComparer
{
    public static IEnumerable<TSource> DistinctBy<TSource,TValue>(
        this IEnumerable<TSource> source,
        Func<TSource, TValue> selector)
    {
        var comparer = ProjectionComparer<TSource>.CompareBy<TValue>(
            selector, EqualityComparer<TValue>.Default);
        return new HashSet<TSource>(source, comparer);
    }
}
public static class ProjectionComparer<TSource>
{
    public static IEqualityComparer<TSource> CompareBy<TValue>(
        Func<TSource, TValue> selector)
    {
        return CompareBy<TValue>(selector, EqualityComparer<TValue>.Default);
    }
    public static IEqualityComparer<TSource> CompareBy<TValue>(
        Func<TSource, TValue> selector,
        IEqualityComparer<TValue> comparer)
    {
        return new ComparerImpl<TValue>(selector, comparer);
    }
    sealed class ComparerImpl<TValue> : IEqualityComparer<TSource>
    {
        private readonly Func<TSource, TValue> selector;
        private readonly IEqualityComparer<TValue> comparer;
        public ComparerImpl(
            Func<TSource, TValue> selector,
            IEqualityComparer<TValue> comparer)
        {
            if (selector == null) throw new ArgumentNullException("selector");
            if (comparer == null) throw new ArgumentNullException("comparer");
            this.selector = selector;
            this.comparer = comparer;
        }

        bool IEqualityComparer<TSource>.Equals(TSource x, TSource y)
        {
            if (x == null && y == null) return true;
            if (x == null || y == null) return false;
            return comparer.Equals(selector(x), selector(y));
        }

        int IEqualityComparer<TSource>.GetHashCode(TSource obj)
        {
            return obj == null ? 0 : comparer.GetHashCode(selector(obj));
        }
    }
}

0 Stimmen

Eine Frage zu den Codes: Was ist ProjectionComparer? Eine .Net-Klasse oder eine LINQ- oder IEnumerable-verwandte Klasse, so dass Sie eine angepasste Erweiterung haben können?

0 Stimmen

GUT. Ich denke, dass "ProjectionComparer" ein beliebiger Klassenname ist, den Sie definiert haben, aber innerhalb der Klasse haben Sie die Erweiterungsmethode DistinctBy() auf IEnumerable angepasst, und ProjectionComparer<T> ist eine weitere Hilfsklasse, richtig? Kann ProjectionComparer<T> einen anderen Namen haben, anstatt den gleichen Namen?

0 Stimmen

Wenn ich eine Liste von Wert1 von MyClass erhalten möchte, kann ich diesen Komparator wie folgt verwenden: List<string> listValue1s = list.Distinct(comparer).ToList().Select(y => y.value1); Ist das richtig?

3voto

Jon Rea Punkte 8959

Sie können diese Erweiterungsmethode verwenden:

    IEnumerable<MyClass> distinctList = sourceList.DistinctBy(x => x.value1);

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

0 Stimmen

Dies scheint zu funktionieren, aber wenn die Methode aufgerufen und nicht sofort ausgewertet wird, zum Beispiel mit ToList() sieht die zurückgegebene Aufzählung bei der ersten Auswertung korrekt aus, aber nach einer zweiten Auswertung ist die Aufzählung leer. Angenommen, ich habe eine Liste von 4 Objekten mit 1 Duplikat und rufe diese Methode auf, um eine Aufzählung zu erhalten, die 3 Objekte enthält. Aufruf von .Count() 収益 3 und ein erneuter Aufruf liefert 0 . Hat jemand eine Idee, was hier los ist?

2voto

dahlbyk Punkte 71222

Überprüfen Sie Enumerable.Distinct() die einen IEqualityComparer akzeptieren kann:

class MyClassComparer<T> : IEqualityComparer<MyClass<T>>
{
    // Products are equal if their names and product numbers are equal.
    public bool Equals(MyClass<T> x, MyClass<T>y)
    {
        // Check whether the compared objects reference the same data.
        if (Object.ReferenceEquals(x, y)) return true;

        // Check whether any of the compared objects is null.
        if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
            return false;

        // Check whether the products' properties are equal.
        return x.value1 == y.value1;
    }

    // If Equals() returns true for a pair of objects,
    // GetHashCode must return the same value for these objects.

    public int GetHashCode(MyClass<T> x)
    {
        // Check whether the object is null.
        if (Object.ReferenceEquals(x, null)) return 0;

        // Get the hash code for the Name field if it is not null.
        return (x.value1 ?? "").GetHashCode();
    }
}

Ihr Codeschnipsel könnte wie folgt aussehen:

List<MyClass<T>> list; 
...
List<MyClass<T>> listDistinct = list.Distinct(new MyClassComparer<T>).ToList();

0 Stimmen

Ich denke, jeder Ansatz hat seine Berechtigung. Der Group-by-Ansatz erfordert den wenigsten Code und kann flexibler sein, hat aber einen (leichten) Leistungsabfall zur Folge, und auf den ersten Blick ist der Zweck des Codes nicht so leicht erkennbar. Marcs allgemeine Lösung liest sich recht flüssig, aber einige könnten sagen, dass ein einziger Ausdruck zu viel tut: Er gibt sowohl an, wie Elemente verglichen werden, als auch, wie das eigentliche select-distinct funktioniert. Mein Ansatz ist spezifischer, bietet aber eine klare Trennung zwischen der Äquivalenzlogik und den Operationen, die sie nutzen.

0 Stimmen

Vielen Dank für Ihre ausführlichen Kommentare. Ich stimme Ihnen zu, was die Lesbarkeit und die Trennung betrifft. Jedoch in Bezug auf die Flexibilität, um verschiedene Instanzen von T durch die zweite oder letzte zu erhalten, erhält der Comparer nur die erste und es könnte zur gleichen Flexibilität in komplexiert werden, richtig? Siehe meine Kommentare zu jpbochi.

1 Stimmen

In der Tat würde der Ansatz Distinct-with-Comparer nur den ersten in der "Menge" zurückgeben. Ich denke jedoch, dass die Semantik von "Distinct" darin besteht, dass die Objekte als gleichwertig betrachtet werden sollten, wenn sie nach Ihren Kriterien übereinstimmen. Sobald man anfängt, den Ersten oder Letzten auszuwählen, hat man sich wirklich von einer "Distinct"-Berechnung zu einer Art Aggregation (Erster, Letzter, Min., was auch immer) auf einer Gruppierung entfernt.

2voto

Arasu RRK Punkte 1060

Das wird einfacher sein...

var distinctList = list.GroupBy(l => l.value1, (key, c) => l.FirstOrDefault());

0 Stimmen

Anstelle von l.FirstOrDefault() sollte es sein c.FirstOrDefault() . Ein ähnlicher Kommentar wurde hinzugefügt zu este Antwort.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X