Проверьте, содержит ли один 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 ().


Это также хорошо для определения того, что находится в 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);

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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.