Vérifiez si un IEnumerable contient tous les éléments d'un autre IEnumerable

102

Quel est le moyen le plus rapide de déterminer si un IEnumerable contient tous les éléments d'un autre IEnumerable lors de la comparaison d'un champ / propriété de chaque élément dans les deux collections?


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
la source
1
De quel côté sont vos listes? Voulez-vous vérifier si tous les éléments de la liste 1 sont dans la liste 2 ou que tous les éléments de la liste 2 sont dans la liste 1?
Mark Byers

Réponses:

138

Il n'y a pas de «moyen rapide» de le faire, sauf si vous suivez et maintenez un état qui détermine si toutes les valeurs d'une collection sont contenues dans une autre. Si vous ne devez IEnumerable<T>travailler que contre, j'utiliserais Intersect.

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

La performance de cela devrait être très raisonnable, car Intersect()il énumérera chaque liste une seule fois. De plus, le deuxième appel à Count()sera optimal si le type sous-jacent est un ICollection<T>plutôt qu'un simple IEnumerable<T>.

Kent Boogaart
la source
J'ai fait quelques tests et cette méthode semble fonctionner plus vite que les autres. Merci pour le conseil.
Brandon Zacharie
2
Cela ne fonctionne pas s'il y a des doublons dans la liste. Par exemple, la comparaison d'un tableau char de 441 et 414 renvoie 41 et donc le comptage échoue.
John
69

Vous pouvez également utiliser Except pour supprimer de la première liste toutes les valeurs qui existent dans la deuxième liste, puis vérifier si toutes les valeurs ont été supprimées:

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

Cette méthode avait l'avantage de ne pas nécessiter deux appels à Count ().

JW
la source
Ceci est également utile pour découvrir ce qui est dans List1 mais pas dans List2;
Homer
16
Cela fonctionne dans les situations où list1 a des valeurs dupliquées. La réponse acceptée ne le fait pas.
dbc
23

C # 3.5+

Utilisation Enumerable.All<TSource>pour déterminer si tous les éléments List2 sont contenus dans List1:

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

Cela fonctionnera également lorsque list1 contient encore plus que tous les éléments de list2.

John K
la source
10
Aïe des implications de performance d'un Contains()appel dans un All()appel.
Kent Boogaart
Vous pouvez également le déplacer vers la méthode de groupe: bool hasAll = list2Uris.All (list1Uris.Contains);
jimpanzer
Dans le cas des types IEnumerable <T>, cette solution fournira des performances n * m.
Dmitriy Dokshin
5
Sténographie: bool hasAll = list2Uris.All(list1Uris.Contains);
Illuminator
3

La réponse de Kent est fine et courte, mais la solution qu'il fournit nécessite toujours une itération sur toute la première collection. Voici le code source:

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

Ce n'est pas toujours nécessaire. Alors, voici ma solution:

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

En fait, vous devriez penser à utiliser ISet<T>( HashSet<T>). Il contient toutes les méthodes d'ensemble requises. IsSubsetOfdans ton cas.

Dmitriy Dokshin
la source
2

l'opérateur Linq SequenceEqual fonctionnerait également (mais est sensible au fait que les éléments de l'énumérable sont dans le même ordre)

return list1Uris.SequenceEqual(list2Uris);
bkaid
la source
2

La solution marquée comme réponse échouerait en cas de répétitions. Si votre IEnumerable ne contient que des valeurs distinctes, il passera.

La réponse ci-dessous est pour 2 listes avec des répétitions:

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

        return aCount == bCount &&
               a.Intersect(b).Count() == aCount;
Chandramouleswaran Ravichandra
la source
Ce n'est pas une bonne solution car elle supprime tous les doublons et ne les compare pas réellement.
John
2

Vous devez utiliser HashSet au lieu de Array.

Exemple:

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

Référence

La seule limitation HasSet est que nous ne pouvons pas obtenir élément par index comme List ni obtenir élément par clé comme les dictionnaires. Tout ce que vous pouvez faire est de les énumérer (pour chacun, while, etc.)

S'il vous plaît laissez-moi savoir si cela fonctionne pour vous

Meule
la source
-2

vous pouvez utiliser cette méthode pour comparer deux listes

    //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
la source
À peu près la même réponse a été donnée 3 ans plus tôt: stackoverflow.com/a/16967827/5282087
Dragomok