Scan - 7 higher order functions
This is the last function in the blog series about higher order functions. I hope that you've enjoyed it so far.
Scan
Scan is very much like a map, but it will take an accumulator through the list. The function signature looks like this.
IEnumerable<U> Scan<T, U>(Func<U, T, U> fn, U state, IEnumerable<T> list)
For each element in the list, we use fn to calculate a new value and update the state. This will become much clearer if we look at an example.
Examples
I've taken this example straight from the F# version of this function. We have an account with an initial balance, and then we have transactions. We would like to get the account history as well as the current balance after the transactions has been made.
// Scan transaction history var accountHistory = transactions.Scan((balance, transaction) => balance + transaction, initialBalance);
// Print Console.WriteLine("Initial balance:\n{0:c}", initialBalance); Console.WriteLine("{0,15} {1,15}", "Transactions", "Balance"); foreach (var balance in transactions.Zip(accountHistory, Tuple.Create)) { Console.WriteLine("{0,15:c} {1,15:c}", balance.Item1, balance.Item2); }
// => // Initial balance: // 1.122,73 kr // Transactions Balance // -100,00 kr 1.022,73 kr // 450,34 kr 1.473,07 kr // -62,34 kr 1.410,73 kr // -127,00 kr 1.283,73 kr // -13,50 kr 1.270,23 kr // -12,92 kr 1.257,31 kr
We use the state variable initialBalance that we bring through the computation of each value, that will result in the transaction history. Quite neat.
My bus to the city departs at alternating 10/20 minutes intervals. I would like a time table that tells me of each departs from now, 10 departs forward.
// First bus departs at 10:09 var firstDeparture = DateTime.Parse("2012-06-23 10:09");
// Get timetable for each departure var timetable = departures.Scan((time, minute) => time.AddMinutes(minute), firstDeparture);
Console.WriteLine("Bus departures at"); foreach (var departure in timetable.Take(10)) { Console.WriteLine(departure.ToString("t")); }
// Bus departures at // 10:09 // 10:29 // 10:39 // 10:59 // 11:09 // 11:29 // 11:39 // 11:59 // 12:09 // 12:29
Implementation
The implementation is extremely simple. There is a result variable that is accumulated through the computation, but aside from that, this is nearly identical to Map.
if (fn == null)
{
throw new ArgumentNullException("fn", "Supplied function to Scan is not allowed to be null");
}
var result = state;
foreach (var item in list)
{
yield return result = fn(result, item);
}
}
public static IEnumerable<U> Scan<T, U>(this IEnumerable<T> list, Func<U, T, U> fn, U state) { return Scan(fn, state, list); }
Conclusion
Scan is one of those odd cases when you think that you need to take to a for loop, but really just want to remember your higher order functions. If your peers don't know about Scan, they'll have to look it up, but that is a better way to communicate intent with your code than custom hacking inline for loops.