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.