【问题标题】:Determine if an IEnumerable<T> contains any object of another IEnumerable<T>确定 IEnumerable<T> 是否包含另一个 IEnumerable<T> 的任何对象
【发布时间】:2009-03-27 19:19:54
【问题描述】:

我有 2 个IEnumerable&lt;int&gt;

IEnumerable<int> x;
IEnumerable<int> y;

确定 x 中是否存在 y 中的任何 int 的最佳方法是什么?
目前我正在使用:

return x.Intersect<int>(y).Count() > 0;

循环遍历并单独测试会明显更快吗?

foreach (int i in x)
{
    foreach (int j in y)
    {
        if (i == j) return true;
    }
}
return false;

列表相对较轻,x 中不超过 50 个整数,y 中不超过 4 个(如果考虑的话)。

【问题讨论】:

    标签: .net ienumerable


    【解决方案1】:

    最好使用Any method 而不是Count method

    return x.Intersect<int>(y).Any();
    

    这假定IEnumerable&lt;int&gt; 实现不也实现ICollection&lt;int&gt;。在这种情况下,Count(在 IEnumerable&lt;T&gt; 实现 ICollection&lt;T&gt; 的情况下)是 O(N) 操作,而 Any总是是 O(1) 操作。 (因为它只检查 single 元素)。但是,Count 的行为是一个实现细节,您不应该依赖它。

    我已经写过更深入的in a blog post,详细介绍了何时使用Count()Any()。总结:

    • 使用Enumerable.Any扩展方法检查序列中是否存在元素。
    • 请勿在与零的比较中使用Enumerable.Count 扩展方法,因为以下在语义上是等效的:
      • sequence.Count() == 0
      • !sequence.Any()
    • 请勿使用Enumerable.Count 扩展方法与“非零”条件进行比较,因为以下在语义上是等效的:
      • sequence.Count != 0
      • sequence.Any()

    【讨论】:

      【解决方案2】:

      编辑:下面的原始答案确实涉及复杂性。如果序列足够短,通过GetNext() 的所有调用、构建HashSet 等实际上将比使用Intersect(y).Any() 更昂贵。但是,在这种情况下,无论如何这两个调用都会非常快。

      换句话说,Intersect(y).Any() 肯定会随着两个序列变长而更好地扩展,但如果您绝对确定序列很短,嵌套循环解决方案会更快。

      原答案

      不,Intersect() 将比双循环解决方案更快 - 但正如 CasperOne 所写,Any() 将比 Count() 更快,因为它一看到元素就会退出。

      假设序列长度为 N 和 M,相交将是 O(N + M),而双环解是 O(N * M)

      Intersect 从“内部”序列构建HashSet(这需要O(M) 复杂度),然后遍历外部序列(需要O(N) 复杂度),产生集合中的任何元素。这些结果是流式传输的 - 当从 Intersect() 请求第一个元素时,将评估内部序列,但这仅适用于找到第一个匹配项(如果有)。使用Any(),如果有任何匹配,您将“提前退出”,因此我们在计算复杂性时不需要考虑匹配的总数。

      来自 LINQ 的流式传输结果 - 值得一试(以及延迟执行)。

      【讨论】:

      • 如果我不启动我的分析器,您能解释一下原因吗?似乎这两个循环的成本会低于计数?
      【解决方案3】:

      Intersect 可以,但正如其他人所说,我不会在结果上调用.Count()

      原因是 Intersect 确实创建了两个列表的交集。它创建了一个IEnumerable,它能够枚举那个交集,但它实际上还没有枚举那些结果。大部分工作都被推迟到您最终迭代此枚举的时候。

      Count 的问题在于它确实会遍历整个枚举。因此,它不仅总是计算所有结果,而且还导致计算这些结果所涉及的所有工作也运行。

      相比之下,调用Any非常快,因为您将在返回之前最多计算一个交集结果。当然,在没有匹配的情况下,它仍然需要迭代整个列表。然而,这并不比你以前更糟。事实上,它仍然更快,因为正如 Jon Skeet 所提到的,Intersect 函数使用 HashSet 来计算结果,而不是遍历所有内容。您的最佳和平均案例得到了极大的改善。

      就像这两个sn-ps的区别:

      int count = 0;
      foreach (int i in x)
      {
         foreach (int j in y)
         {
            if (i==j) count++;
         }
      }
      return (count > 0);
      

      .

      // this one should look familiar
      foreach (int i in x)
      {
          foreach (int j in y)
          {
             if (i==j) return true;
          }
      }
      return false;
      

      显然第二个平均要快得多。 Any() 的性能类似于(由于 HashSet,不一样)第二个 sn-p,而 Count() 将类似于第一个。

      【讨论】:

        【解决方案4】:

        你最好这样做:

        return x.Intersect(y).Any();
        

        一旦找到一个匹配项,它将立即中止,并停止枚举集合。

        【讨论】:

        • 不是先做相交,再检查有没有元素吗?
        • @Josh:不,因为流式传输结果。
        • Josh:在 MSDN 中查找 LINQ 中的“延迟执行”。值得花时间了解它的工作原理和原因,以及如何利用它来发挥自己的优势。
        • @Reed:这不是延迟执行,而是流式传输结果。它们有些不同。例如,OrderBy 被延迟,但一旦被要求提供第一个结果,它就必须处理 all 的所有结果。然后,它当然会一一产生结果。相交是半流半缓冲区。
        • @Jon Skeet:感谢您的澄清。
        【解决方案5】:

        这个问题和最后一个答案比我的答案早 1 年;但是,我的发现与公认的答案不同。我发现循环比使用 Intersect.Any() 快得多。也许我的基准代码不正确?

        这是我用来查找 Intersect、嵌套循环和 IndexOf 循环之间每秒迭代次数的代码。请原谅VB。 ;)

        Dim ArrayLength As Integer = 50
        Dim doesContain As Boolean = False
        Dim counter As Integer = 0
        Dim sw As New System.Diagnostics.Stopwatch()
        Dim BigArray1 As String() = New String(ArrayLength) {}
        Dim BigArray2 As String() = New String(ArrayLength) {}
        Dim rand As New Random(DateTime.Now.Millisecond)
        For i As Integer = 0 To ArrayLength
            BigArray1(i) = Convert.ToChar(rand.Next(0, 255)).ToString()
            BigArray2(i) = Convert.ToChar(rand.Next(0, 255)).ToString()
        Next
        Dim AnEnumerable1 As IEnumerable(Of String) = BigArray1
        Dim AnEnumerable2 As IEnumerable(Of String) = BigArray2
        
        counter = 0
        sw.Restart()
        Do
            doesContain = False
            For Each x As String In AnEnumerable1
                For Each y As String In AnEnumerable2
                    If x.Equals(y) Then
                        doesContain = True
                        Exit For
                    End If
                Next
                If doesContain Then Exit For
            Next
            counter += 1
        Loop While sw.ElapsedMilliseconds < 1000
        Console.WriteLine("InnerLoop iterated:  " & counter.ToString() & " times in " & sw.ElapsedMilliseconds.ToString() & "ms.")
        
        counter = 0
        sw.Restart()
        Do
            doesContain = AnEnumerable1.Intersect(AnEnumerable2).Any()
            counter += 1
        Loop While sw.ElapsedMilliseconds < 1000
        Console.WriteLine("Intersect iterated:  " & counter.ToString() & " times in " & sw.ElapsedMilliseconds.ToString() & "ms.")
        
        counter = 0
        sw.Restart()
        Do
            For Each x As String In AnEnumerable1
                If Array.IndexOf(Of String)(BigArray2, x) <> -1 Then
                    Exit For
                End If
            Next
            counter += 1
        Loop While sw.ElapsedMilliseconds < 1000
        Console.WriteLine("IndexOf iterated:    " & counter.ToString() & " times in " & sw.ElapsedMilliseconds.ToString() & "ms.")
        

        我的结果超过了两个 5,000,000 项数组。迭代次数越高越好:

        • InnerLoop 迭代:1000 毫秒内 2974553 次。
        • 相交迭代:1168 毫秒内 4 次。
        • IndexOf 迭代:1000 毫秒内 4194423 次。

        我的结果超过了两个 50 项数组。迭代次数越高越好:

        • InnerLoop 迭代:1000 毫秒内 375712 次。
        • 相交迭代:1000 毫秒内 203721 次。
        • IndexOf 迭代:1000 毫秒内 668421 次。

        【讨论】:

        • 我刚刚注意到,如果我调整两个数组中匹配项的概率,则 Intersect 的性能会显着提高。早期比赛的机会越少,相交越快。
        • 这方面的一个大问题是您要多次迭代其中一个可枚举项;无论性能影响如何,您都可能会产生意想不到的副作用(如果有数据库调用支持呢?)。
        猜你喜欢
        • 2018-02-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-12-23
        • 1970-01-01
        • 2012-12-19
        • 1970-01-01
        相关资源
        最近更新 更多