使用C#/ Linq累积序列的子序列

我正在尝试找到一种更好的方法来处理基于以下要求的数字序列: sequence[i]的值是它自己的值加上从sequence[0]sequence[i-1]的累积的总和。

例如:如果序列是列表

 List list = new List { 10.0, 20.0, 30.0, 40.0 }; 

输出结果应该是

 list[0] = 10.0 list[1] = 20.0 + 10.0 list[2] = 30.0 + 10.0 + 20.0 list[3] = 40.0 + 10.0 + 20.0 + 30.0 

我知道使用多次迭代的蛮力方式,但我想知道必须有一些更好的解决方案(可能使用LINQ)。

假设您可以访问LINQ:

 using System.Linq; List numbers = new List {10, 20, 30, 40}; List runningTotals = new List(numbers.Count); numbers.Aggregate(0, (sum, value) => { sum += value; runningTotals.Add(sum); return sum; }); 

我的版本是对注释中的内容进行修改并返回IEnumerable而不是List但ToList()将对其进行排序。

应该非常有效率。 谁不喜欢使用yield return ? 😉

 public IEnumerable GetCumulativeSequence (IEnumerable input) { var runningTotal = 0.0; foreach (double current in input) { runningTotal+=current; yield return runningTotal; } } void Main() { List list = new List { 10.0, 20.0, 30.0, 40.0 }; var foo = GetCumulativeSequence(list); } 

这样做的主要优点是它只在输入数组上进行一次循环。 如果你实际上没有使用所有返回的东西(即你只看前三个)那么它将不会计算其余的东西。 在更长的名单等方面可能有用。对于像Chris Doggett这样的答案,可能会有同样的说法,但并不是所有使用linq的人都这么说。

使用相对未知的Select重载,可以看到索引:

 var result = list.Select((val, index) => list.Take(index + 1).Sum()) 

一个非LINQ版本,只是为了与众不同:

 List GetSums(List values) { List sums = new List(); double total = 0; foreach(double d in values) { total += d; sums.Add(total); } return sums; } 

这是另一种类似于其中一个post的方式,但这个是自包含的:

 // C# Enumerable.Range(1, 100) .Aggregate(new List{0}, (acc, x) => { acc.Add(acc.Last() + x); return acc; }) .Dump(); // Dump using LINQPad; ans: 5050 

如果我们切换到int64,并使用10,000,000作为上限,并读取最终累积结果,则代码在Corei5上执行大约450毫秒,最终输出为:

500000.05亿

顺便提一下,对于1,000,000的上限,执行大约是40毫秒,因此将大小增加10会使时间增加10。

我构建了一个基于APL扫描运算符的扩展方法(具有很多变体)的扩展方法(类似于Aggregate本质上是APL reduce运算符),它返回操作序列的中间结果:

 public static IEnumerable Scan(this IEnumerable src, TResult seed, Func combine) { foreach (var s in src) { seed = combine(seed, s); yield return seed; } } 

然后可以使用它,如:

 var ans = list.Scan(0.0, (a, d) => a+d).ToList();