使用LINQ查找C#中数字数组的累积和

我有一个包含双精度的csv字符串(例如“0.3,0.4,0.3”),我希望能够输出一个包含这些数字累积和的双数组(例如[0.3,0.7,1.0])。

到目前为止,我有

double[] probabilities = textBox_f.Text.Split(new char[]{','}).Select(s => double.Parse(s)).ToArray();

它将数字作为数组给出,但不是数字的累积和。

有没有办法继续这个表达式来获得我想要的东西,或者我是否需要使用迭代从我已经拥有的数组创建一个新的数组?

 var input=new double[]{ ... } double sum=0; var output=input .Select(w=>sum+=w); 

有一般性的时间,并且有时间解决实际提出的问题。 这是后者之一。 如果你想创建一个方法,将一系列双精度变成一个部分和的序列,那么就这样做:

 public static IEnumerable CumulativeSum(this IEnumerable sequence) { double sum = 0; foreach(var item in sequence) { sum += item; yield return sum; } } 

简单。 没有弄乱聚合和复杂的查询和诸如此类的东西。 易于理解,易于调试,易于使用:

 textBox_f.Text .Split(new char[]{','}) .Select(s => double.Parse(s)) .CumulativeSum() .ToArray(); 

现在,我注意到如果这是用户输入,那么double.Parse可以抛出exception; 做以下事情可能是一个更好的主意:

 public static double? MyParseDouble(this string s) { double d; if (double.TryParse(s, out d)) return d; return null; } public static IEnumerable CumulativeSum(this IEnumerable sequence) { double? sum = 0; foreach(var item in sequence) { sum += item; yield return sum; } } ... textBox_f.Text .Split(new char[]{','}) .Select(s => s.MyParseDouble()) .CumulativeSum() .ToArray(); 

如果用户输入错误,现在你不会得到exception; 你得到零。

我前段时间有类似的要求。 基本上,我需要进行聚合,但我还需要选择每个中间值。 所以我编写了一个名为SelectAggregate的扩展方法(可能不是最合适的名称,但我找不到更好的东西)可以像这样使用:

 double[] numbers = new [] { 0.3, 0.4, 0.3 }; double[] cumulativeSums = numbers.SelectAggregate(0.0, (acc, x) => acc + x).ToArray(); 

这是代码:

  public static IEnumerable SelectAggregate( this IEnumerable source, TAccumulate seed, Func func) { source.CheckArgumentNull("source"); func.CheckArgumentNull("func"); return source.SelectAggregateIterator(seed, func); } private static IEnumerable SelectAggregateIterator( this IEnumerable source, TAccumulate seed, Func func) { TAccumulate previous = seed; foreach (var item in source) { TAccumulate result = func(previous, item); previous = result; yield return result; } } 

您希望使用Aggregate运算符,并使用List作为聚合累加器。 这样你就可以产生一个本身就是一系列总和的投影。

这是一个让你入门的例子:

 double[] runningTotal = textBox_f.Text .Split(new char[]{','}) .Select(s => double.Parse(s)) .Aggregate((IEnumerable)new List(), (a,i) => a.Concat(new[]{a.LastOrDefault() + i})) .ToArray(); 

为什么需要LINQ?

 var cumulative = new double[probabilities.Length]; for (int i = 0; i < probabilities.Length; i++) cumulative[i] = probabilities[i] + (i == 0 ? 0 : cumulative[i-1]); 

首先,我认为对Linq来说这不是一件好事。 普通的老foreach会做得更好。 但作为一个谜题,它很好。

第一个想法是使用子查询,但我不喜欢它,因为它是O(n ^ 2)。 这是我的线性解决方案:

  double[] probabilities = new double[] { 0.3, 0.4, 0.3}; probabilities .Aggregate( new {sum=Enumerable.Empty(), last = 0.0d}, (a, c) => new { sum = a.sum.Concat(Enumerable.Repeat(a.last+c,1)), last = a.last + c }, a => a.sum ); 

使用RX:

 var input=new double[]{ ... } var output = new List(); input.ToObservable().Scan((e, f) => f + e).Subscribe(output.Add); 

这是使用LINQ执行此操作的一种方法:

 double[] doubles = { 1.7, 2.3, 1.9, 4.1, 2.9 }; var doublesSummed = new List(); Enumerable.Aggregate(doubles, (runningSum, nextFactor) => { double currentSum = runningSum + nextFactor; doublesSummed.Add(currentSum); return currentSum; }); doublesSummed.Dump(); 

在LINQPad中:

  • 4
  • 5.9
  • 10
  • 12.9

这实际上非常简单,可以使用生成器进行概括。 这是一个名为Accumulate的新扩展方法,它的工作方式类似于SelectAggregate 。 它通过将二元函数应用于序列中的每个元素并且到目前为止累积值来返回新序列。

  public static class EnumerableHelpers { public static IEnumerable Accumulate(this IEnumerable self, U init, Func f) { foreach (var x in self) yield return init = f(init, x); } public static IEnumerable Accumulate(this IEnumerable self, Func f) { return self.Accumulate(default(T), f); } public static IEnumerable PartialSums(this IEnumerable self) { return self.Accumulate((x, y) => x + y); } public static IEnumerable PartialSums(this IEnumerable self) { return self.Accumulate((x, y) => x + y); } }