项目顺序,以便每个元素将成为LINQ之前的所有元素的总和

时间:2012-11-21 06:54:21

标签: c# linq

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

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()。

3 个答案:

答案 0 :(得分: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 project确实在其ScanPreScan成员中对此有所支持。在这种情况下,您需要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;
    }
}

正如您已经注意到的,这基本上与您的原始代码相同 - 但是被懒惰地评估并适用于任何浮动序列。

答案 1 :(得分: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;
});

答案 2 :(得分:1)

Microsoft的Reactive Extensions团队发布了一个“Interactive Extensions”库,为IEnumerable<T>添加了许多有用的扩展。其中一个是Scan,它完全符合您的要求。

以下是执行总计的IX方式:

IEnumerable<float> results = items.Scan(0.0f, (x1, x2) => x1 + x2);