使用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();