使用LINQ查找C#中数字数组的累积总和
-
26-10-2019 - |
题
我有一个包含双打的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<double> CumulativeSum(this IEnumerable<double> 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可以抛出异常。做类似的事情可能是一个更好的主意:
public static double? MyParseDouble(this string s)
{
double d;
if (double.TryParse(s, out d))
return d;
return null;
}
public static IEnumerable<double?> CumulativeSum(this IEnumerable<double?> 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();
现在,如果用户犯了打字错误,您将不再例外。你得到零。
一段时间以前,我有类似的要求。基本上,我需要进行聚合,但是我还需要选择每个中间值。所以我写了一个名为的扩展方法 SelectAggregate
(可能不是最合适的名称,但我找不到任何更好的名称)可以这样使用:
double[] numbers = new [] { 0.3, 0.4, 0.3 };
double[] cumulativeSums = numbers.SelectAggregate(0.0, (acc, x) => acc + x).ToArray();
这是代码:
public static IEnumerable<TAccumulate> SelectAggregate<TSource, TAccumulate>(
this IEnumerable<TSource> source,
TAccumulate seed,
Func<TAccumulate, TSource, TAccumulate> func)
{
source.CheckArgumentNull("source");
func.CheckArgumentNull("func");
return source.SelectAggregateIterator(seed, func);
}
private static IEnumerable<TAccumulate> SelectAggregateIterator<TSource, TAccumulate>(
this IEnumerable<TSource> source,
TAccumulate seed,
Func<TAccumulate, TSource, TAccumulate> func)
{
TAccumulate previous = seed;
foreach (var item in source)
{
TAccumulate result = func(previous, item);
previous = result;
yield return result;
}
}
您想使用 Aggregate
操作员,带有 List<double>
作为聚合蓄能器。这样,您可以产生一个本身就是总和的投影。
这是一个让您入门的示例:
double[] runningTotal = textBox_f.Text
.Split(new char[]{','})
.Select(s => double.Parse(s))
.Aggregate((IEnumerable<double>)new List<double>(),
(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<double>(), 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<double>();
input.ToObservable().Scan((e, f) => f + e).Subscribe(output.Add);
实际上,使用发电机可以概括一下。这是一种称为新的扩展方法 Accumulate
这就像结合 Select
和 Aggregate
. 。它通过将二进制函数应用于序列中的每个元素和累积值来返回新序列。
public static class EnumerableHelpers
{
public static IEnumerable<U> Accumulate<T, U>(this IEnumerable<T> self, U init, Func<U, T, U> f)
{
foreach (var x in self)
yield return init = f(init, x);
}
public static IEnumerable<T> Accumulate<T>(this IEnumerable<T> self, Func<T, T, T> f)
{
return self.Accumulate(default(T), f);
}
public static IEnumerable<double> PartialSums(this IEnumerable<double> self)
{
return self.Accumulate((x, y) => x + y);
}
public static IEnumerable<int> PartialSums(this IEnumerable<int> self)
{
return self.Accumulate((x, y) => x + y);
}
}
这是 一个 使用linq这样做的方法:
double[] doubles = { 1.7, 2.3, 1.9, 4.1, 2.9 };
var doublesSummed = new List<double>();
Enumerable.Aggregate(doubles, (runningSum, nextFactor) => {
double currentSum = runningSum + nextFactor;
doublesSummed.Add(currentSum);
return currentSum;
});
doublesSummed.Dump();
在linqpad中:
- 4
- 5.9
- 10
- 12.9
累积总和 List<double>
:
var nums = new List<double>() { 0.3, 0.0, 0.4, 1.1 };
var cumsum = nums.Aggregate(new List<double> (),
(list, next) => { list.Add(list.LastOrDefault() + next); return list; });