【问题标题】:C# aggregate in a better time complexityC# 以更好的时间复杂度聚合
【发布时间】:2015-12-06 10:33:29
【问题描述】:

假设我们有这样一个数组:

var arr = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

我要进行聚合:

var sum = arr.Aggregate((a, b) => a + b);

当然,这只是一个简化的例子。我不处理int,而是需要合并的更复杂的对象(它们是树)。然而,这种聚合的效果真的很糟糕,因为它从左到右迭代,添加了两个相互欺骗的元素。在整数的情况下,这没有任何区别,但在复杂对象的情况下,更好的解决方案是以树的方式执行聚合。什么意思?

                   55
              36         19
      10             26      19
  3       7      11      15      19
1   2   3   4   5   6   7   8   9   10

我希望这个架构能说明问题。

如何在 C# 的 LINQ 中实现这一点?

【问题讨论】:

  • 但是……复杂度不一样吗?您仍然可以遍历每个对象一次。
  • 这将是相同的,但可以并行运行。
  • 嗯,是的...但是当我处理大树时,每次合并时都会复制它们。因此,第一棵树中的元素将被复制 n - 1 次。即使它们可以被复制 log n 次。
  • 因为在此期间它们会更小。例如,在线性列表中,最后一次合并它复制了一个几乎完整的树,但在 log-n-approach 的情况下,它合并了两个相似大小的树。这是一个显着的差异。

标签: c# linq aggregate


【解决方案1】:

您可以使用PLINQ(并行LINQ)

并行聚合模式使用未共享的局部变量,这些变量在计算结束时合并以提供最终结果。将非共享的局部变量用于局部、局部计算的结果是循环的步骤可以变得彼此独立的方式。

如果您调用 AsParallel 扩展方法,您将指示编译器绑定到 PLINQ 而不是 LINQ。该程序将使用表达式中所有进一步查询操作的并行版本。

在你的情况下,代码是:

var sum = arr.AsParallel().Aggregate((a, b) => a + b);

在这里您可以找到更多信息:https://msdn.microsoft.com/en-us/library/ff963547.aspx

【讨论】:

    【解决方案2】:

    您可以创建一个类似于 LINQ 的扩展方法,该方法以您描述的分层方式“自上而下”聚合序列。但是,为了提高效率,这需要随机访问源序列,因此在 IEnumerable<T> 之上构建并不是最佳选择。但是您可以使用 IReadOnlyList<T> 作为替代方案(当然,这要求您的源存储在数组或列表中)。

    static class ReadOnlyListExtensions {
    
      public static T HierarchicalAggregate<T>(this IReadOnlyList<T> source, Func<T, T, T> func) {
        if (source == null)
          throw new ArgumentNullException("source");
        if (func == null)
          throw new ArgumentNullException("func");
        if (source.Count == 0)
          throw new InvalidOperationException("Sequence contains no elements");
        return Recurse(source, 0, source.Count, func);
      }
    
      static T Recurse<T>(this IReadOnlyList<T> source, Int32 startIndex, Int32 count, Func<T, T, T> func) {
        if (count == 1)
          return source[startIndex];
        var leftCount = count/2;
        var leftAggregate = Recurse(source, startIndex, leftCount, func);
        var rightCount = count - leftCount;
        var rightAggregate = Recurse(source, startIndex + leftCount, rightCount, func);
        return func(leftAggregate, rightAggregate);
      }
    
    }
    

    请注意,与您的示例相比,此算法执行的除法略有不同。在第一层,10 个元素的序列被分成两个 5 个元素的序列,然后每个序列又被分成一个 2 和一个 3 元素的序列,以此类推:

    55 15 + 40 3 + 12 13+27 1+2 3+9 6+7 8+19 4+5 9+10

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-10-31
      • 1970-01-01
      • 1970-01-01
      • 2022-06-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多