检查一个 IEnumable 是否包含另一个 IEnumable 的所有元素

在比较两个集合中每个元素的字段/属性时,确定一个 IEnumable 是否包含另一个 IEnumable 的所有元素的最快方法是什么?


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
88632 次浏览

Linq 运算符 Sequenceequals 也可以工作(但是对可枚举项的顺序是否相同比较敏感)

return list1Uris.SequenceEqual(list2Uris);

C # 3.5 +

使用 Enumerable.All<TSource>确定列表1中是否包含所有列表2项:

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

当 list1包含的内容甚至超过 list2的所有项时,这也会起作用。

除非跟踪并维护某种状态,以确定一个集合中的所有值是否包含在另一个集合中,否则不存在这样做的“快速方法”。如果你只有 IEnumerable<T>对工作,我会使用 Intersect

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

这种方法的性能应该非常合理,因为 Intersect()只会枚举每个列表一次。另外,如果底层类型是 ICollection<T>而不仅仅是 IEnumerable<T>,那么对 Count()的第二次调用将是最佳的。

还可以使用“除外”从第一个列表中删除第二个列表中存在的所有值,然后检查是否已删除所有值:

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

此方法的优点是不需要对 Count ()进行两次调用。

标记为答案的解决方案在重复的情况下会失败。如果 IEnumable 只包含不同的值,那么它将传递。

下面的答案是2个重复的列表:

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


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

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>)。它包含所有必需的集合方法。

应该使用 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 这样的索引获取 item,也不能通过像 Dictionaries 这样的 Key 获取 item。你所能做的就是枚举它们(对于每一个,同时,等等)

另一种方法是将超集列表转换为 HashSet并使用 HashSetIsSuperSet方法。

bool Contains(IEnumerable<Item> list1, IEnumerable<Item>, list2)
{
var list1Values = list1.Select(item => item.Value);
var list2Values = list2.Select(item => item.Value).ToHashSet();


return list2Values.IsSupersetOf(list1Values);
}