web-dev-qa-db-fra.com

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

Quel est le moyen le plus rapide pour 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
92
Brandon Zacharie

Il n'y a pas de "méthode rapide" pour ce faire, sauf si vous suivez et conservez un état qui détermine si toutes les valeurs d'une collection sont contenues dans une autre. Si vous n'avez que IEnumerable<T> Pour travailler, j'utiliserais Intersect.

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

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

122
Kent Boogaart

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 ().

48
J W

C # 3.5+

En utilisant Enumerable.All<TSource> pour déterminer si tous les éléments de 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.

19
John K

La réponse de Kent est fine et courte, mais la solution qu'il propose 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. Voici donc 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. IsSubsetOf dans votre cas.

3
Dmitriy Dokshin

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

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

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

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

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

return list1Uris.SequenceEqual(list2Uris);
2
bkaid

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 de HasSet est que nous ne pouvons pas obtenir un élément par index comme Liste ni obtenir un élément par clé comme Dictionnaires. Tout ce que vous pouvez faire est de les énumérer (pour chacun, pendant, etc.)

Veuillez me faire savoir si cela fonctionne pour vous

1
Rick