Controlla se un IEnumerable contiene tutti gli elementi di un altro IEnumerable


102

Qual è il modo più veloce per determinare se un oggetto IEnumerable contiene tutti gli elementi di un altro oggetto IEnumerable quando si confronta un campo / una proprietà di ogni elemento in entrambe le raccolte?


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
Da che parte sono le tue liste? Vuoi controllare se tutti gli elementi nell'elenco 1 sono nell'elenco 2 o che tutti gli elementi nell'elenco 2 sono nell'elenco 1?
Mark Byers

Risposte:


138

Non esiste un "modo rapido" per farlo a meno che non si tenga traccia e si mantenga uno stato che determina se tutti i valori in una raccolta sono contenuti in un'altra. Se devi solo IEnumerable<T>lavorare contro, io userei Intersect.

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

Le prestazioni di questo dovrebbero essere molto ragionevoli, poiché Intersect()verranno enumerate su ciascuna lista solo una volta. Inoltre, la seconda chiamata a Count()sarà ottimale se il tipo sottostante è un ICollection<T>piuttosto che solo un IEnumerable<T>.


Ho fatto alcuni test e questo metodo sembra funzionare più velocemente degli altri. Grazie per il consiglio.
Brandon Zacharie

2
Questo non funziona se sono presenti duplicati nell'elenco. Ad esempio, il confronto di un array di caratteri 441 e 414 restituisce 41 e quindi il conteggio non riesce.
John

69

È inoltre possibile utilizzare Tranne per rimuovere dal primo elenco tutti i valori presenti nel secondo elenco, quindi verificare se tutti i valori sono stati rimossi:

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

Questo metodo aveva il vantaggio di non richiedere due chiamate a Count ().


Questo è utile anche per scoprire cosa c'è in List1 ma non in List2;
Homer

16
Funziona in situazioni in cui list1 ha valori duplicati. La risposta accettata no.
dbc

23

C # 3.5+

Usando Enumerable.All<TSource>per determinare se tutti gli elementi di List2 sono contenuti in List1:

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

Funzionerà anche quando list1 contiene anche più di tutti gli elementi di list2.


10
Ouch per le implicazioni sulle prestazioni di una Contains()chiamata all'interno di una All()chiamata.
Kent Boogaart

Inoltre puoi spostarlo nel metodo di gruppo: bool hasAll = list2Uris.All (list1Uris.Contains);
jimpanzer

In caso di tipi IEnumerable <T> questa soluzione fornirà prestazioni n * m.
Dmitriy Dokshin

5
Stenografia: bool hasAll = list2Uris.All(list1Uris.Contains);
Illuminatore

3

La risposta di Kent è buona e breve, ma la soluzione che fornisce richiede sempre l'iterazione dell'intera prima raccolta. Ecco il codice sorgente:

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

Questo non è sempre richiesto. Quindi, ecco la mia soluzione:

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

In realtà, dovresti pensare di usare ISet<T>( HashSet<T>). Contiene tutti i metodi set richiesti.IsSubsetOfnel tuo caso.


2

anche l'operatore Linq SequenceEqual funzionerebbe (ma è sensibile al fatto che gli elementi dell'enumerabile siano nello stesso ordine)

return list1Uris.SequenceEqual(list2Uris);

2

La soluzione contrassegnata come risposta fallirebbe in caso di ripetizioni. Se il tuo IEnumerable contiene solo valori distinti, allora passerà.

La risposta seguente è per 2 elenchi con ripetizioni:

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

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

Questa non è una buona soluzione poiché rimuove tutti i duplicati e non li confronta effettivamente.
Giovanni

2

Dovresti usare HashSet invece di Array.

Esempio:

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

Riferimento

L'unica limitazione di HasSet è che non possiamo ottenere elemento per indice come List né ottenere elemento per chiave come dizionari. Tutto quello che puoi fare è enumerarli (per ciascuno, mentre, ecc.)

Per favore fatemi sapere se funziona per voi


-2

puoi usare questo metodo per confrontare due elenchi

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

Praticamente la stessa risposta è stata data 3 anni prima: stackoverflow.com/a/16967827/5282087
Dragomok
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.