Cumulating Values with LINQ






4.33/5 (2 votes)
This is an alternative for "Cumulating values with LINQ"
Introduction
This is a fully generic implementation of the tip described by Mika Wendelius.
Using the Code
The simple extension method which handles a simple accumulation (like the CumulativeSum
of the original tip) is this:
/// <summary>
/// Transforms a sequence into a new sequence by accumulating the values by a specified method.
/// </summary>
/// <typeparam name="Titems">
/// The type of the items in the initial and result sequences, and the accumulated value.
/// </typeparam>
/// <param name="sequence">The input sequence.
/// </param>
/// <param name="accumulateOperation">The operation which accumulates the values.
/// The parameters are (previousAccumulatedValue, itemFromSequence).
/// </param>
/// <returns>A new sequence of the accumulated values.
/// </returns>
public static IEnumerable<Titems> CumulativeSequence<Titems>
(this IEnumerable<Titems> sequence,
Func<Titems, Titems, Titems> accumulateOperation)
{
return CumulativeSequence(sequence, default(Titems), accumulateOperation, a => a);
}
This clearly just defers to a fully parameterized implementation (which is necessary to implement the CumulativePath
functionality of the original tip):
/// <summary>
/// Transforms a sequence into a new sequence by accumulating the values by a specified method.
/// </summary>
/// <typeparam name="Titems">
/// The type of the items in the initial sequence.
/// </typeparam>
/// <typeparam name="Taccum">
/// The type of the accumulator object.
/// </typeparam>
/// <typeparam name="Tvalues">
/// The type of the values in the result sequence.
/// </typeparam>
/// <param name="sequence">The input sequence.
/// </param>
/// <param name="accumulatorInitializer">
/// The initializer for the accumulator object.
/// </param>
/// <param name="accumulateOperation">The operation which accumulates the values.
/// The parameters are (accumulatorObject, itemFromSequence).
/// It returns the updated or new accumulatorObject.
/// </param>
/// <param name="valueOperation">
/// The operation which gets the result sequence value from the accumulator object.
/// </param>
/// <returns>A new sequence of the accumulated values.
/// </returns>
public static IEnumerable<Tvalues> CumulativeSequence<Titems, Taccum,
Tvalues>(this IEnumerable<Titems> sequence,
Taccum accumulatorInitializer,
Func<Taccum, Titems, Taccum> accumulateOperation,
Func<Taccum, Tvalues> valueOperation)
{
Taccum accumulator = accumulatorInitializer;
return sequence.Select(item => {
accumulator = accumulateOperation(accumulator, item);
return valueOperation(accumulator);
});
}
(using
statements are omitted, see the attached code file.)
Here is the usage corresponding to the CumulativeSum
(these give the same output as the original tip):
private static decimal Sum(decimal a, decimal b) { return a + b; }
static void Main()
{
decimal[] numbers = new decimal[] { 1, 3, 5, 7, 11 };
foreach (decimal partialSum in numbers.CumulativeSequence((a, n) => a + n))
{
Debug.Print(" - {0}", partialSum);
}
// can use a defined method instead of repeating the same lambda
Debug.Print("The cumulative sum total is {0}", numbers.CumulativeSequence(Sum).Last());
// ...
The CumulativePath
of the original tip used a StringBuilder
for the path accumulation, and is implemented like this:
// Some random path
var splitPath = @"C:\Some directory\Some subdirectory\Somefile.txt".Split('\\');
// Split the path and print out each cumulated portion of the path
Debug.Print("The path contains the following parts");
var pathSequence = splitPath.CumulativeSequence(new StringBuilder(),
(a, p) => {
if (a.Length != 0)
a.Append('\\');
a.Append(p);
return a;
},
a => a.ToString());
foreach (string partialPath in pathSequence)
{
Debug.Print(" - '{0}'", partialPath);
}
}
History
- July 27, 2012 - Initial alternative