Проверьте, содержит ли один IEnumerable все элементы другого IEnumerable

103

Каков самый быстрый способ определить, содержит ли один IEnumerable все элементы другого IEnumerable при сравнении поля / свойства каждого элемента в обеих коллекциях?


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
Каковы ваши списки? Вы хотите проверить, все ли элементы в списке 1 находятся в списке 2 или все элементы в списке 2 находятся в списке 1?
Марк Байерс

Ответы:

140

Не существует "быстрого способа" сделать это, если вы не отслеживаете и не поддерживаете какое-то состояние, определяющее, все ли значения в одной коллекции содержатся в другой. Если вам нужно IEnumerable<T>работать только против, я бы использовал Intersect.

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

Производительность этого должна быть очень разумной, поскольку Intersect()будет выполнять перечисление по каждому списку только один раз. Кроме того, второй вызов Count()будет оптимальным, если базовый тип - это, ICollection<T>а не просто IEnumerable<T>.

Кент Бугарт
источник
Я провел несколько тестов, и этот метод работает быстрее, чем другие. Спасибо за чаевые.
Брэндон Захари
2
Это не работает, если в списке есть дубликаты. Например, сравнение массива символов 441 и 414 возвращает 41, и, следовательно, счет не выполняется.
Джон
69

Вы также можете использовать Except, чтобы удалить из первого списка все значения, которые существуют во втором списке, а затем проверить, все ли значения были удалены:

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

Преимущество этого метода состоит в том, что он не требует двух вызовов Count ().

JW
источник
Это также хорошо для определения того, что находится в List1, но не в List2;
Homer
16
Это работает в ситуациях, когда list1 имеет повторяющиеся значения. Принятый ответ - нет.
dbc
23

C # 3.5+

Использование, Enumerable.All<TSource>чтобы определить, все ли элементы List2 содержатся в List1:

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

Это также будет работать, когда список1 содержит даже больше, чем все элементы списка2.

Джон К.
источник
10
Ой, влияние Contains()вызова на производительность при All()вызове.
Kent Boogaart
Также вы можете переместить его в групповой метод: bool hasAll = list2Uris.All (list1Uris.Contains);
jimpanzer
В случае типов IEnumerable <T> это решение обеспечит производительность n * m.
Дмитрий Докшин
5
Сокращение: bool hasAll = list2Uris.All(list1Uris.Contains);
Illuminator
3

Ответ Кента хорош и краток, но решение, которое он предлагает, всегда требует итерации по всей первой коллекции. Вот исходный код:

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

Это не всегда требуется. Итак, вот мое решение:

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

Собственно, вам стоит подумать об использовании ISet<T>( HashSet<T>). Он содержит все необходимые методы набора. IsSubsetOfв твоем случае.

Дмитрий Докшин
источник
2

оператор Linq SequenceEqual также будет работать (но чувствителен к перечисляемым элементам, находящимся в том же порядке)

return list1Uris.SequenceEqual(list2Uris);
bkaid
источник
2

Решение, отмеченное как ответ, не сработает в случае повторения. Если ваш IEnumerable содержит только отдельные значения, он пройдет.

Приведенный ниже ответ предназначен для двух списков с повторениями:

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

        return aCount == bCount &&
               a.Intersect(b).Count() == aCount;
Чандрамулесваран Равичандра
источник
Это не лучшее решение, поскольку оно удаляет все дубликаты, а не сравнивает их.
Джон
2

Вы должны использовать HashSet вместо Array.

Пример:

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

Ссылка

Единственное ограничение HasSet заключается в том, что мы не можем получить элемент по индексу, например List, или получить элемент по ключу, например Dictionaries. Все, что вы можете сделать, это перечислить их (для каждого, пока и т. Д.)

Пожалуйста, дайте мне знать, работает ли это для вас

Рик
источник
-2

вы можете использовать этот метод для сравнения двух списков

    //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;
    }
Сатистский
источник
Практически такой же ответ был дан тремя годами ранее: stackoverflow.com/a/16967827/5282087
Dragomok 07