Überprüfen Sie, ob eine IEnumerable alle Elemente einer anderen IEnumerable enthält


102

Was ist der schnellste Weg, um festzustellen, ob eine IEnumerable alle Elemente einer anderen IEnumerable enthält, wenn ein Feld / eine Eigenschaft jedes Elements in beiden Sammlungen verglichen wird?


public class Item
{
    public string Value;

    public Item(string value)
    {
        Value = value;
    }
}

//example usage

Item[] List1 = {new Item("1"),new Item("a")};
Item[] List2 = {new Item("a"),new Item("b"),new Item("c"),new Item("1")};

bool Contains(IEnumerable<Item> list1, IEnumerable<Item>, list2)
{
    var list1Values = list1.Select(item => item.Value);
    var list2Values = list2.Select(item => item.Value);

    return //are ALL of list1Values in list2Values?
}

Contains(List1,List2) // should return true
Contains(List2,List1) // should return false

1
Wie sind Ihre Listen? Möchten Sie überprüfen, ob sich alle Elemente in Liste1 in Liste 2 befinden oder ob sich alle Elemente in Liste2 in Liste 1 befinden?
Mark Byers

Antworten:


138

Es gibt keinen "schnellen Weg", dies zu tun, es sei denn, Sie verfolgen und pflegen einen Status, der bestimmt, ob alle Werte in einer Sammlung in einer anderen enthalten sind. Wenn Sie nur dagegen arbeiten müssen IEnumerable<T>, würde ich verwenden Intersect.

var allOfList1IsInList2 = list1.Intersect(list2).Count() == list1.Count();

Die Leistung sollte sehr vernünftig sein, da Intersect()über jede Liste nur einmal aufgezählt wird. Außerdem ist der zweite Aufruf von Count()optimal, wenn der zugrunde liegende Typ ein ICollection<T>und nicht nur ein ist IEnumerable<T>.


Ich habe einige Tests durchgeführt und diese Methode scheint schneller zu laufen als die anderen. Danke für den Tipp.
Brandon Zacharie

2
Dies funktioniert nicht, wenn die Liste Duplikate enthält. Zum Beispiel ergibt der Vergleich eines char-Arrays von 441 und 414 41 und somit schlägt die Zählung fehl.
John

69

Sie können auch Außer verwenden, um alle in der zweiten Liste vorhandenen Werte aus der ersten Liste zu entfernen und dann zu überprüfen, ob alle Werte entfernt wurden:

var allOfList1IsInList2 = !list1.Except(list2).Any();

Diese Methode hatte den Vorteil, dass keine zwei Aufrufe von Count () erforderlich waren.


Dies ist auch gut, um herauszufinden, was in Liste1, aber nicht in Liste2 enthalten ist.
Homer

16
Dies funktioniert in Situationen, in denen list1 doppelte Werte hat. Die akzeptierte Antwort nicht.
dbc

23

C # 3.5+

Verwenden Sie, Enumerable.All<TSource>um festzustellen, ob alle List2-Elemente in List1 enthalten sind:

bool hasAll = list2Uris.All(itm2 => list1Uris.Contains(itm2));

Dies funktioniert auch, wenn list1 noch mehr als alle Elemente von list2 enthält.


10
Autsch bei den Auswirkungen auf die Leistung eines Contains()Anrufs innerhalb eines All()Anrufs.
Kent Boogaart

Sie können es auch in die Gruppenmethode verschieben: bool hasAll = list2Uris.All (list1Uris.Contains);
Jimpanzer

Bei IEnumerable <T> -Typen bietet diese Lösung n * m-Leistung.
Dmitriy Dokshin

4
Kurzschrift: bool hasAll = list2Uris.All(list1Uris.Contains);
Illuminator

3

Kents Antwort ist gut und kurz, aber die Lösung, die er bietet, erfordert immer eine Iteration über die gesamte erste Sammlung. Hier ist der Quellcode:

public static IEnumerable<TSource> Intersect<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer)
{
    if (first == null)
        throw Error.ArgumentNull("first");
    if (second == null)
        throw Error.ArgumentNull("second");
    return Enumerable.IntersectIterator<TSource>(first, second, comparer);
}

private static IEnumerable<TSource> IntersectIterator<TSource>(IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer)
{
    Set<TSource> set = new Set<TSource>(comparer);
    foreach (TSource source in second)
        set.Add(source);
    foreach (TSource source in first)
    {
        if (set.Remove(source))
            yield return source;
    }
}

Das ist nicht immer erforderlich. Also, hier ist meine Lösung:

public static bool Contains<T>(this IEnumerable<T> source, IEnumerable<T> subset, IEqualityComparer<T> comparer)
{
    var hashSet = new HashSet<T>(subset, comparer);
    if (hashSet.Count == 0)
    {
        return true;
    }

    foreach (var item in source)
    {
        hashSet.Remove(item);
        if (hashSet.Count == 0)
        {
            break;
        }
    }

    return hashSet.Count == 0;
}

Eigentlich sollten Sie über die Verwendung von ISet<T>( HashSet<T>) nachdenken . Es enthält alle erforderlichen Set-Methoden. IsSubsetOfin deinem Fall.


2

Der Linq-Operator SequenceEqual würde ebenfalls funktionieren (ist jedoch empfindlich dafür, dass die Elemente der Aufzählung in derselben Reihenfolge vorliegen).

return list1Uris.SequenceEqual(list2Uris);

2

Die als Antwort gekennzeichnete Lösung würde bei Wiederholungen fehlschlagen. Wenn Ihre IEnumerable nur unterschiedliche Werte enthält, wird sie übergeben.

Die folgende Antwort gilt für 2 Listen mit Wiederholungen:

        int aCount = a.Distinct().Count();
        int bCount = b.Distinct().Count();

        return aCount == bCount &&
               a.Intersect(b).Count() == aCount;

Dies ist keine gute Lösung, da alle Duplikate entfernt und nicht tatsächlich verglichen werden.
John

2

Sie sollten HashSet anstelle von Array verwenden.

Beispiel:

List1.SetEquals(List2); //returns true if the collections contains exactly same elements no matter the order they appear in the collection

Referenz

Die einzige HasSet-Einschränkung besteht darin, dass wir weder Artikel wie Index noch Artikel wie Schlüssel wie Wörterbücher abrufen können. Alles, was Sie tun können, ist, sie aufzuzählen (für jede, während usw.)

Bitte lassen Sie mich wissen, ob das für Sie funktioniert


-2

Mit dieser Methode können Sie zwei Listen vergleichen

    //Method to compare two list
    private bool Contains(IEnumerable<Item> list1, IEnumerable<Item> list2)
    {
        bool result;

        //Get the value
        var list1WithValue = list1.Select(s => s.Value).ToList();
        var list2WithValue = list2.Select(s => s.Value).ToList();

        result = !list1WithValue.Except(list2WithValue).Any();

        return result;
    }

Ziemlich die gleiche Antwort wurde 3 Jahre zuvor gegeben: stackoverflow.com/a/16967827/5282087
Dragomok
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.