【问题标题】:Check if one IEnumerable contains all elements of another IEnumerable检查一个 IEnumerable 是否包含另一个 IEnumerable 的所有元素
【发布时间】:2010-07-18 06:17:34
【问题描述】:

在比较两个集合中每个元素的字段/属性时,确定一个 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

【问题讨论】:

  • 您的清单是哪条路?您要检查 list1 中的所有项目是否都在列表 2 中,还是 list2 中的所有项目都在列表 1 中?

标签: c# .net linq ienumerable


【解决方案1】:

没有“快速方法”可以做到这一点,除非您跟踪和维护确定一个集合中的所有值是否包含在另一个集合中的某种状态。如果你只有IEnumerable&lt;T&gt; 可以对抗,我会使用Intersect

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

这样的表现应该是非常合理的,因为Intersect() 只会对每个列表进行一次枚举。此外,如果基础类型是 ICollection&lt;T&gt; 而不仅仅是 IEnumerable&lt;T&gt;,则对 Count() 的第二次调用将是最佳的。

【讨论】:

  • 我做了一些测试,这种方法似乎比其他方法运行得更快。感谢您的提示。
  • 如果列表中有重复项,这将不起作用。例如比较 441 和 414 的 char 数组返回 41,因此计数失败。
【解决方案2】:

您也可以使用 except 从第一个列表中删除第二个列表中存在的所有值,然后检查是否所有值都已删除:

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

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

【讨论】:

  • 这对于找出 List1 中有什么但 List2 没有;
  • 这适用于 list1 具有重复值的情况。接受的答案没有。
  • 现在如何处理包含?意思是 list1.item[0].contains(list[0] and list[1] ...)
【解决方案3】:

C# 3.5+

使用Enumerable.All&lt;TSource&gt;判断List1中是否包含所有List2项:

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

当 list1 包含的项目比 list2 的所有项目还要多时,这也有效。

【讨论】:

  • All() 调用中的Contains() 调用对性能的影响。
  • 您也可以将其移至组方法:bool hasAll = list2Uris.All(list1Uris.Contains);
  • IEnumerable 类型的情况下,此解决方案将提供 n*m 性能。
  • 速记: bool hasAll = list2Uris.All(list1Uris.Contains);
【解决方案4】:

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&lt;T&gt;HashSet&lt;T&gt;)。它包含所有必需的设置方法。 IsSubsetOf 在你的情况下。

【讨论】:

    【解决方案5】:

    标记为答案的解决方案在重复的情况下会失败。如果您的 IEnumerable 仅包含不同的值,那么它将通过。

    以下答案适用于 2 个重复的列表:

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

    【讨论】:

    • 这不是一个好的解决方案,因为它会删除所有重复项而不是实际比较它们。
    【解决方案6】:

    你应该使用 HashSet 而不是 Array。

    例子:

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

    Reference

    唯一的 HasSet 限制是我们不能像 List 那样按索引获取项目,也不能像字典那样按 Key 获取项目。您所能做的就是枚举它们(对于每个,同时等)

    【讨论】:

    • SetEquals 没有回答这个问题;问题正在寻找“list1 IsSubsetOf list2”或“list2 IsSuperSetOf lost1”
    【解决方案7】:

    Linq 运算符 SequenceEqual 也可以工作(但对可枚举项的顺序相同很敏感)

    return list1Uris.SequenceEqual(list2Uris);
    

    【讨论】:

    • SequenceEqual 没有回答这个问题。问题是寻找在相等或更多列表 2 元素中存在的一些 list1 元素。只有当两个列表具有相同数量的值时,SequenceEqual 才能返回 true
    【解决方案8】:

    另一种方法是将您的超集列表转换为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);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-04
      • 2019-05-05
      相关资源
      最近更新 更多