Проверьте, содержит ли один IEnumerable все элементы другого IEnumerable
Каков самый быстрый способ определить, содержит ли один 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
Для этого не существует "быстрого пути", если вы не отслеживаете и не поддерживаете какое-либо состояние, которое определяет, содержатся ли все значения в одной коллекции в другой. Если у вас есть только IEnumerable<T>
для работы, я бы использовал Intersect
.
var allOfList1IsInList2 = list1.Intersect(list2).Count() == list1.Count();
Эффективность этого должна быть очень разумной, так как Intersect()
будет перечислять по каждому списку только один раз. Кроме того, второй вызов Count()
будет оптимальным, если базовый тип - это ICollection<T>
, а не только IEnumerable<T>
.
Ответ 2
Вы также можете использовать исключение из первого списка всех значений, существующих во втором списке, а затем проверить, были ли удалены все значения:
var allOfList1IsInList2 = !list1.Except(list2).Any();
Этот метод имел то преимущество, что не требовал двух вызовов Count().
Ответ 3
С# 3.5 +
Используя Enumerable.All<TSource>
, чтобы определить, содержатся ли все элементы List2 в List1:
bool hasAll = list2Uris.All(itm2 => list1Uris.Contains(itm2));
Это также будет работать, когда list1 содержит даже больше, чем все элементы списка2.
Ответ 4
Решение, помеченное как ответ, потерпит неудачу в случае повторений. Если ваш IEnumerable содержит только определенные значения, он будет проходить.
Ниже приведен ответ для 2 списков с повторениями:
int aCount = a.Distinct().Count();
int bCount = b.Distinct().Count();
return aCount == bCount &&
a.Intersect(b).Count() == aCount;
Ответ 5
Ответ Kent - прекрасный и короткий, но решение, которое он обеспечивает, всегда требует итерации по всей первой коллекции. Вот исходный код:
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
в вашем случае.
Ответ 6
оператор Linq SequenceEqual также будет работать (но чувствителен к тому, что перечисляемые элементы находятся в одном порядке)
return list1Uris.SequenceEqual(list2Uris);
Ответ 7
вы можете использовать этот метод для сравнения двух списков
//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;
}