可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have a csv string containing doubles (e.g "0.3,0.4,0.3"), and I want to be able to output a double array containing the cumulative sum of these numbers (e.g [0.3,0.7,1.0]).
So far, I have
double[] probabilities = textBox_f.Text.Split(new char[]{','}).Select(s => double.Parse(s)).ToArray();
which gives the numbers as an array, but not the cumulative sum of the numbers.
Is there any way to continue this expression to get what I want, or do I need to use iteration to create a new array from the array I already have?
回答1:
var input=new double[]{ ... }
double sum=0;
var output=input
.Select(w=>sum+=w);
回答2:
There's a time for generality, and there's a time for solving the problem actually posed. This is one of the latter times. If you want to make a method that turns a sequence of doubles into a sequence of partial sums, then just do that:
public static IEnumerable<double> CumulativeSum(this IEnumerable<double> sequence)
{
double sum = 0;
foreach(var item in sequence)
{
sum += item;
yield return sum;
}
}
Easy. No messing around with aggregates and complicated queries and whatnot. Easy to understand, easy to debug, easy to use:
textBox_f.Text
.Split(new char[]{','})
.Select(s => double.Parse(s))
.CumulativeSum()
.ToArray();
Now, I note that if that is user input then double.Parse can throw an exception; it might be a better idea to do something like:
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();
and now you don't get an exception if the user makes a typing mistake; you get nulls.
回答3:
I had a similar requirement some time ago. Basically, I needed to do an aggregation, but I also needed to select each intermediate value. So I wrote an extension method named SelectAggregate
(probably not the most appropriate name, but I couldn't find anything better then) that can be used like that:
double[] numbers = new [] { 0.3, 0.4, 0.3 };
double[] cumulativeSums = numbers.SelectAggregate(0.0, (acc, x) => acc + x).ToArray();
Here's the code :
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;
}
}
回答4:
You want to use the Aggregate
operator, with a List<double>
as the aggregation accumulator. That way you can produce a projection which is itself a sequence of sums.
Here's an example to get you started:
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();
回答5:
Why does it need to be 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]);
回答6:
First of all i don't think that it is good task for Linq. Plain old foreach
will do it better. But as a puzzle it is fine.
First idea was to use subqueries, but i don't like it, because it is O(n^2). Here is my linear solution:
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
);
回答7:
use RX :
var input=new double[]{ ... }
var output = new List<double>();
input.ToObservable().Scan((e, f) => f + e).Subscribe(output.Add);
回答8:
Here's a way of doing it using 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();
In LINQPad:
回答9:
This is actually pretty straightforward to generalize using generator. Here is a new extension method called Accumulate
that works like a combination of Select
and Aggregate
. It returns a new sequence by applying a binary function to each element in the sequence and accumulated value so far.
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);
}
}