Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

using LINQ to find the cumulative sum of an array of numbers in C#

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?

like image 888
simonalexander2005 Avatar asked Jan 28 '11 00:01

simonalexander2005


4 Answers

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.

like image 197
Eric Lippert Avatar answered Oct 16 '22 01:10

Eric Lippert


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;
        }
    }
like image 41
Thomas Levesque Avatar answered Oct 16 '22 00:10

Thomas Levesque


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();
like image 5
LBushkin Avatar answered Oct 15 '22 23:10

LBushkin


var input=new double[]{ ... }
double sum=0;

var output=input
    .Select(w=>sum+=w);
like image 2
Blindy Avatar answered Oct 15 '22 23:10

Blindy