Functional one-liner for running totals in C#
Visualizing some data earlier this week I had to compute the running total of a sequence of numbers.
For example, if the input sequence was [ 100; 50; 25 ] the result of the computation would be a new sequence of [ 100; 150; 175 ].
Muscle memory made me take a procedural approach, which works, but made me wonder if I could get away with less lines of code and without mutable state.
static IEnumerable<int> ToRunningTotalProcedural(IEnumerable<int> sequence)
{
var runningTotal = 0;
foreach (var item in sequence)
{
runningTotal += item;
yield return runningTotal;
}
}
Although C# doesn’t try very hard to push a functional approach, the BCL does give you some useful tools.
The first thing that comes to mind is using IEnumerable’s Aggregate function, which will apply a function over each item in the sequence and will pass the aggregated partial result the next time the function is applied. Each time the function is applied, we can take the last item (if it exists) of the aggregated partial result and add the current item’s value to it, and append that sum to the aggregated partial result.
static IEnumerable<int> ToRunningTotalFunctionalAggregate(IList<int> sequence)
{
return sequence.Aggregate(
ImmutableList<int>.Empty,
(acc, item) => rt.Add(acc.Any() ? acc.Last() + item : item));
}
Another more compact - but less efficient approach - I could think of, is using the index of each element in the sequence, to take subsets and to sum their values.
static IEnumerable<int> ToRunningTotalLinqTake(IList<int> sequence)
{
return sequence.Select((item, i) => sequence.Take(i + 1).Sum());
}
Running out of ideas, I ported F#‘s Scan function which allows more compact code, without giving up efficiency. This function, similar to the Aggregate function, applies a function over each item in the sequence. However, instead of passing the aggregated partial result each time the function is applied, the value of the last computation is passed in, to finally return the list of all computations.
With a bit of good will, C# allows you to be more functional too.