0

我有以下代码将数组的每个元素转换为之前所有元素的总和。程序实现如下:

float[] items = {1, 5, 10, 100}; //for example
float[] sums = new float[items.Length];
float total = 0;
for(int i = 0; i < items.Length; i++){
  total+=items[i];
  sums[i] = total;
}

我将如何将其实现为 LINQ 单线?我知道它可以做到,例如

items.Select((x, i) => items.Take(i + 1).Sum())

但我认为当数组大小增加时效率不是很高,因为它必须对每个元素执行 Sum() 。

4

3 回答 3

5

老实说,LINQ 并不能非常干净地支持这种情况 - 您需要聚合和投影的混合。你可以用副作用来做到这一点,这很可怕:

// Don't use this!
float sum = 0f;
var sums = items.Select(x => sum +=x).ToArray();

LINQ 的副作用很糟糕。同样,您可以使用Take/来执行此操作Sum,如 RePierre 和 LB 所示 - 但这需要一个自然为O(N) 的操作并将其转换为 O(N^2) 的操作。

我不久前开始的MoreLINQ 项目Scan在其和PreScan成员中确实对此提供了支持。在这种情况下,你想要Scan,我相信:

var sums = items.Scan((x, y) => x + y);

如果不想使用第三方库,不想使用副作用,不想Take解决方案效率低下,需要添加,需要针对单一类型(例如float在你的情况)你可以很容易地介绍你自己的方法:

public static IEnumerable<float> RunningSum(this IEnumerable<float> source)
{
    if (source == null)
    {
        throw new ArgumentNullException(source);
    }
    float sum = 0f;
    foreach (var item in source)
    {
        sum += item;
        yield return sum;
    }
}

正如您会注意到的,这与您的原始代码基本相同 - 但被延迟评估并适用于任何浮点序列。

于 2012-11-21T06:59:03.053 回答
1
var result = items.Select((item, index) => items.Take(index).Sum() + item);

编辑 您可以使用Aggregate方法来创建总和:

var result = items.Aggregate(new List<float>(), (seed, item) =>
{
    seed.Add(seed.LastOrDefault() + item);
    return seed;
});
于 2012-11-21T06:58:14.873 回答
1

Microsoft 的 Reactive Extensions 团队发布了一个“Interactive Extensions”库,它为IEnumerable<T>. 其中之一就是Scan完全符合您的要求。

这是计算总计的 IX 方式:

IEnumerable<float> results = items.Scan(0.0f, (x1, x2) => x1 + x2);
于 2012-11-21T07:51:47.190 回答