【问题标题】:Linq IEnumerable Extension Method - How to improve performance?Linq IEnumerable 扩展方法 - 如何提高性能?
【发布时间】:2012-11-26 05:10:29
【问题描述】:

我编写了以下扩展方法,它查找满足传递给它的谓词的连续项序列。序列中连续项的数量由参数'sequenceSize决定。

例如,我可能有一个 IEnumerable 整数,我想找到 10 个大于 100 的连续值。此扩展方法将确定是否存在这样的序列。

这种方法效果很好。但是,由于它必须做的事情,如果 IEnumerable 中有大量元素,它可能会很慢,因为它必须从第一个元素开始,寻找满足谓词的连续值,然后转到第二个元素并执行同样的等等。

我正在寻找有关如何加快速度的建议。我尝试使用 AsParallel() 但这没有影响。

public static IEnumerable<IEnumerable<T>> FindSequenceConsecutive<T>(this IEnumerable<T> sequence, 
                                                                     Predicate<T> predicate, 
                                                                     int sequenceSize)
{
    IEnumerable<T> current = sequence;

    while (current.Count() > sequenceSize)
    {
        IEnumerable<T> window = current.Take(sequenceSize);

        if (window.Where(x => predicate(x)).Count() >= sequenceSize)
            yield return window;

        current = current.Skip(1);
    }
}

【问题讨论】:

    标签: linq


    【解决方案1】:

    此方法运行缓慢的最可能原因是重复调用.Count(),这将立即枚举序列以确定元素的数量。

    您最好明确测试标准并跟踪计数,而不是重复使用Where()Count()

    一般来说,这个方法是枚举序列很多。如果您调用.ToList() 枚举序列一次,然后对生成的列表执行操作,您可能会体验到很好的加速。 (请注意,如果您希望在无限长序列上使用此方法,这将不起作用。)

    更新:您正在测试&gt;= sequenceSize,即使window.Count() == sequenceSize。也就是说,你只需要All()

    if (window.All(x => predicate(x)))
        yield return window;
    

    不确定这有多大帮助,但至少在语义上更清晰。

    进一步编辑:考虑这种方法:

    public static IEnumerable<IEnumerable<T>> FindSequenceConsecutive<T>(this IEnumerable<T> sequence, Predicate<T> predicate, int sequenceSize)
    {
        List<T> list = sequence.ToList();
        List<bool> matchList = list.Select(x => predicate(x)).ToList();
    
        int start = 0;
        int count = list.Count;
    
        while (start + sequenceSize <= count)
        {
            var range = matchList.GetRange(start, sequenceSize);
            if (range.All(x => x))
                yield return list.GetRange(start, sequenceSize);
    
            start++;
        }
    }
    

    它评估一次序列,然后划分必要的列表。

    【讨论】:

    • +1。 All 是否会产生可衡量的影响,我不能说,但这至少意味着你不会击中序列中的每个元素。如果您有一长串不符合过滤谓词的元素,这可能是一个很大的优势。
    • @dlev - 我看不出你的解决方案是如何工作的。我正在寻找连续的值。您的“matchList”实现似乎无法找到连续值。
    • 绝对有效;我已经测试过了:) matchList 本质上是对序列的每个成员调用谓词的结果的缓存。 matchList[i] == predicate(list[i])
    • @dlev - 它确实有效,而且速度非常快。干得好!谢谢。
    【解决方案2】:

    我想这样的事情可能对你有用,因为你可以遍历列表一次,基本上维护一个连续项目的队列,通过谓词,必要时清除(全部)和出列(一个)。

    public static IEnumerable<IEnumerable<T>> FindSequenceConsecutive<T>(this IEnumerable<T> sequence, Predicate<T> predicate, int sequenceSize)
    {
        var queue = new Queue<T>();
    
        foreach (T item in sequence)
        {
            if (predicate(item))
            {
                queue.Enqueue(item);
                if (queue.Count == sequenceSize)
                {
                    yield return queue.ToList();
                    queue.Dequeue();
                }
            }
            else
            {
                queue.Clear();
            }
        }
    }
    

    这么写

    int[] array = { 1, 2, 3, 4, 5, 2, 8, 3, 5, 6 };
    foreach (var seq in array.FindSequenceConsecutive(i => i > 2, 3))
    {
        Console.WriteLine(string.Join(",", seq));
    }
    

    产量

    3,4,5
    8,3,5
    3,5,6
    

    【讨论】:

      【解决方案3】:

      我相信此解决方案将提供最佳性能,并且随着序列变大,扩展性更好,因为它不分配任何额外的缓冲区(列表或队列),也不必将结果转换为列表或执行任何操作依靠结果缓冲区。另外,它只对序列进行一次迭代。

      public static IEnumerable<IEnumerable<T>> FindSequenceConsecutive<T>(this IEnumerable<T> sequence,
          Predicate<T> predicate, int sequenceSize)
      {
          IEnumerable<T> window = Enumerable.Repeat(default(T), 0);
      
          int count = 0;
      
          foreach (var item in sequence)
          {
              if (predicate(item))
              {
                  window = window.Concat(Enumerable.Repeat(item, 1));
                  count++;
      
                  if (count == sequenceSize)
                  {
                      yield return window;
                      window = window.Skip(1);
                      count--;
                  }
              }
              else
              {
                  count = 0;
                  window = Enumerable.Repeat(default(T), 0);
              }                
          }
      }
      

      【讨论】:

      • 这是一个很好的努力,但这错过了序列。如果您有 5 个连续项目通过谓词 ([a, b, c, d, e]) 并且正在寻找 3 的序列,您将得到 [a, b, c],但不是 [b, c, d ] 和 [c, d, e]。其次,我不确定可扩展性的说法,但我不能太挑剔,因为我绝不是专家。但是像 Enumerable.Repeat 这样的方法也会产生垃圾,创建和填充类。 Linq 不是免费的。
      • @Anthony 你发表了我的评论!我实际上赞成你的回答,我很确定这是最好的选择。
      • @Anthony Shoot,你是对的。还需要添加Skip() 或其他内容。呜呜。将只是回滚,吉姆可以处理它。 :)
      • @Anthony - 修复了错误,现在它与您的解决方案基本相同,但更冗长:-(,您的更优雅。我对两者都进行了测试,即使在非常大的序列中,它们实际上也与您的解决方案相同解决方案在原始序列中超过 26K 元素以 1ms 获胜!所以我想这将归结为结果呈现方式的偏好。在您的解决方案中,“窗口”已经在返回的序列中实现,在我的中,它们在有人对其进行迭代之前尚未渲染。确定哪个是更好的方法现在真的超出了我的时间:-) 两者都很好
      • @Jim - 两者都非常好。但是,我实际上更喜欢在迭代之前不实现序列,因为在很多情况下我不关心序列中的内容,我只需要知道序列是否存在。
      猜你喜欢
      • 1970-01-01
      • 2019-03-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-07-24
      • 2010-11-03
      • 1970-01-01
      相关资源
      最近更新 更多