Monday, March 28, 2016

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.

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.

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.

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.

3 comments:

  1. System.Linq.Enumerable.Aggregate is also a very good builder when used with immutable value objects ;-)

    ReplyDelete
  2. You should take a look at Reactive Extensions. IObservable is a nice fit for running totals, particularly if you've got an event source feeding values in or are processing infinite sequences.

    ReplyDelete
    Replies
    1. That's a good point, but I guess it's mostly relevant for data that's not at rest?

      Delete