Sprawdź, czy jeden element IEnumerable zawiera wszystkie elementy innego elementu IEnumerable

103

Jaki jest najszybszy sposób określenia, czy jeden element IEnumerable zawiera wszystkie elementy innego elementu IEnumerable podczas porównywania pola / właściwości każdego elementu w obu kolekcjach?


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
Brandon Zacharie
źródło
1
W którą stronę są twoje listy? Czy chcesz sprawdzić, czy wszystkie elementy z listy 1 znajdują się na liście 2, czy też wszystkie elementy z listy 2 znajdują się na liście 1?
Mark Byers,

Odpowiedzi:

140

Nie ma na to „szybkiego sposobu”, chyba że śledzisz i utrzymujesz pewien stan, który określa, czy wszystkie wartości w jednej kolekcji znajdują się w innej. Jeśli musisz tylko IEnumerable<T>przeciwdziałać, użyłbym Intersect.

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

Wykonanie tego powinno być bardzo rozsądne, ponieważ Intersect()wyliczę na każdej liście tylko raz. Ponadto drugie wywołanie Count()będzie optymalne, jeśli typem bazowym jest, ICollection<T>a nie tylko IEnumerable<T>.

Kent Boogaart
źródło
Zrobiłem kilka testów i ta metoda wydaje się działać szybciej niż inne. Dzięki za wskazówkę.
Brandon Zacharie
2
To nie działa, jeśli na liście znajdują się duplikaty. Na przykład porównanie tablicy znaków 441 i 414 zwraca 41 i dlatego zliczanie kończy się niepowodzeniem.
John
69

Możesz również użyć opcji Wyjątek, aby usunąć z pierwszej listy wszystkie wartości, które istnieją na drugiej liście, a następnie sprawdzić, czy wszystkie wartości zostały usunięte:

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

Ta metoda miała tę zaletę, że nie wymagała dwóch wywołań funkcji Count ().

JW
źródło
Jest to również dobre, aby dowiedzieć się, co jest na Liście1, ale nie na Liście2;
Homer
16
Działa to w sytuacjach, gdy lista1 ma zduplikowane wartości. Przyjęta odpowiedź nie.
dbc
23

C # 3.5+

Używanie Enumerable.All<TSource>do określenia, czy wszystkie elementy z listy List2 znajdują się na liście List1:

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

Będzie to również działać, gdy lista1 zawiera nawet więcej niż wszystkie elementy z listy2.

John K.
źródło
10
Aha, jeśli chodzi o wpływ Contains()połączenia na wydajność podczas All()rozmowy.
Kent Boogaart
Możesz również przenieść go do metody grupowej: bool hasAll = list2Uris.All (list1Uris.Contains);
jimpanzer
W przypadku typów IEnumerable <T> to rozwiązanie zapewni wydajność n * m.
Dmitriy Dokshin
5
Stenogram: bool hasAll = list2Uris.All(list1Uris.Contains);
Illuminator
3

Odpowiedź Kenta jest dobra i krótka, ale rozwiązanie, które zapewnia, zawsze wymaga powtórzenia całej pierwszej kolekcji. Oto kod źródłowy:

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

Nie zawsze jest to wymagane. Oto moje rozwiązanie:

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

Właściwie powinieneś pomyśleć o użyciu ISet<T>( HashSet<T>). Zawiera wszystkie wymagane metody zestawu. IsSubsetOfw Twoim przypadku.

Dmitriy Dokshin
źródło
2

Operator Linq SequenceEqual również działałby (ale jest wrażliwy na elementy wyliczalne w tej samej kolejności)

return list1Uris.SequenceEqual(list2Uris);
bkaid
źródło
2

Rozwiązanie oznaczone jako odpowiedź zawiodłoby w przypadku powtórzeń. Jeśli twój IEnumerable zawiera tylko różne wartości, to zostanie przekazany.

Poniższa odpowiedź dotyczy 2 list z powtórzeniami:

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

        return aCount == bCount &&
               a.Intersect(b).Count() == aCount;
Chandramouleswaran Ravichandra
źródło
Nie jest to dobre rozwiązanie, ponieważ usuwa wszystkie duplikaty, a nie porównuje ich w rzeczywistości.
John
2

Powinieneś użyć HashSet zamiast Array.

Przykład:

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

Odniesienie

Jedynym ograniczeniem HasSet jest to, że nie możemy uzyskać elementu według indeksu, takiego jak Lista, ani uzyskać elementu według klucza, takiego jak Dictionaries. Wszystko, co możesz zrobić, to je wyliczyć (dla każdego, podczas itd.)

Daj mi znać, jeśli to Ci odpowiada

Stóg
źródło
-2

możesz użyć tej metody do porównania dwóch list

    //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;
    }
Sathish
źródło
Prawie tej samej odpowiedzi udzielono 3 lata wcześniej: stackoverflow.com/a/16967827/5282087
Dragomok,