Compruebe si un IEnumerable contiene todos los elementos de otro IEnumerable

102

¿Cuál es la forma más rápida de determinar si un IEnumerable contiene todos los elementos de otro IEnumerable al comparar un campo / propiedad de cada elemento en ambas colecciones?


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
fuente
1
¿En qué sentido están tus listas? ¿Quiere comprobar si todos los elementos de list1 están en la lista 2 o si todos los elementos de list2 están en la lista 1?
Mark Byers

Respuestas:

138

No existe una "forma rápida" de hacer esto a menos que realice un seguimiento y mantenga algún estado que determine si todos los valores de una colección están contenidos en otra. Si solo tienes IEnumerable<T>que trabajar en contra, usaría Intersect.

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

El rendimiento de este debe ser muy razonable, ya que Intersect()se enumerará sobre cada lista una sola vez. Además, la segunda llamada a Count()será óptima si el tipo subyacente es un en ICollection<T>lugar de solo un IEnumerable<T>.

Kent Boogaart
fuente
Hice algunas pruebas y este método parece funcionar más rápido que los demás. Gracias por el consejo.
Brandon Zacharie
2
Esto no funciona si hay duplicados en la lista. Por ejemplo, comparar una matriz de caracteres de 441 y 414 devuelve 41 y, por lo tanto, el recuento falla.
Juan
69

También puede usar Excepto para eliminar de la primera lista todos los valores que existen en la segunda lista y luego verificar si se han eliminado todos los valores:

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

Este método tenía la ventaja de no requerir dos llamadas a Count ().

JW
fuente
Esto también es bueno para averiguar qué hay en List1 pero no en List2;
Homer
16
Esto funciona en situaciones en las que list1 tiene valores duplicados. La respuesta aceptada no lo hace.
dbc
23

C # 3.5+

Utilizando Enumerable.All<TSource>para determinar si todos los elementos de List2 están contenidos en List1:

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

Esto también funcionará cuando list1 contenga incluso más que todos los elementos de list2.

John K
fuente
10
¡Ay de las implicaciones de rendimiento de una Contains()llamada dentro de una All()llamada!
Kent Boogaart
También puede moverlo al método de grupo: bool hasAll = list2Uris.All (list1Uris.Contains);
jimpanzer
En el caso de los tipos IEnumerable <T>, esta solución proporcionará un rendimiento n * m.
Dmitriy Dokshin
5
Taquigrafía: bool hasAll = list2Uris.All(list1Uris.Contains);
Iluminador
3

La respuesta de Kent es buena y breve, pero la solución que proporciona siempre requiere iteración sobre toda la primera colección. Aquí está el código fuente:

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

Eso no siempre es obligatorio. Entonces, aquí está mi solución:

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 realidad, deberías pensar en usar ISet<T>( HashSet<T>). Contiene todos los métodos de configuración necesarios. IsSubsetOfen tu caso.

Dmitriy Dokshin
fuente
2

el operador de Linq SequenceEqual también funcionaría (pero es sensible a que los elementos del enumerable estén en el mismo orden)

return list1Uris.SequenceEqual(list2Uris);
bkaid
fuente
2

La solución marcada como la respuesta fallaría en el caso de repeticiones. Si su IEnumerable solo contiene valores distintos, pasará.

La siguiente respuesta es para 2 listas con repeticiones:

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

        return aCount == bCount &&
               a.Intersect(b).Count() == aCount;
Chandramouleswaran Ravichandra
fuente
Esta no es una buena solución, ya que elimina todos los duplicados y no los compara.
Juan
2

Debería utilizar HashSet en lugar de Array.

Ejemplo:

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

Referencia

La única limitación de HasSet es que no podemos obtener elemento por índice como Lista ni obtener elemento por clave como Diccionarios. Todo lo que puede hacer es enumerarlos (para cada uno, mientras, etc.)

Por favor, avíseme si eso funciona para usted

Almiar
fuente
-2

puede utilizar este método para comparar dos listas

    //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
fuente
Casi la misma respuesta se dio 3 años antes: stackoverflow.com/a/16967827/5282087
Dragomok