LINQ provides several aggregation extension methods: Aggregate, Average, Count, LongCount, Max, Min and Sum. The aggregation methods all take a list of objects and reduces that list to a single result. Conceptually it helps to think of Aggregate as a generic building block and the others aggregation methods (Average, Max, etc.) as special cases of Aggregate. In functional programming languages, such as F#, Aggregate is usually named fold (or inject in Ruby). The SQL like name Aggregate leads developers to write off Aggregate as purely for numeric aggregation purposes. In fact, Aggregate can be used whenever we want to build a single object from a group of objects.
So how does Aggregate work?
Looking at the Aggregate method signature is a pretty scary experience:
public static TAccumulate Aggregate<TSource, TAccumulate>( this IEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate, TSource, TAccumulate> func )
The Aggregate methods takes a list of source objects, a seed value and an accumulator function which it processes as follows:
- The accumulator function is called for each item in the list and returns a value
- The first time the accumulator function is called the seed and the first item in the list are passed to it
- The accumulator function is called again with the result of the first accumulator function call and the second item in the list as its parameters
- This continues until all items in the list are processed
- The result of the last call to the accumulator function is returned as the result of the entire Aggregate method
This can take some getting your head around. Here is an example:
var whiskeyNames = new [] {"Ardbeg 1998", "Glenmorangie","Talisker", "Cragganmore"}; var listOfWhiskies = whiskeyNames.Aggregate("Whiskies: ", (accumulated, next) => { Console.Out.WriteLine("(Adding [{0}] to the list [{1}])", next, accumulated); return accumulated + " " + next; }); Console.Out.WriteLine(listOfWhiskies);
This outputs:
(Adding [Ardbeg 1998] to the list [Whiskies: ]) (Adding [Glenmorangie] to the list [Whiskies: Ardbeg 1998]) (Adding [Talisker] to the list [Whiskies: Ardbeg 1998 Glenmorangie]) (Adding [Cragganmore] to the list [Whiskies: Ardbeg 1998 Glenmorangie Talisker]) Whiskies: Ardbeg 1998 Glenmorangie Talisker Cragganmore
The seed parameter is optional. If it is omitted the first two items in the list will be passed to the function, as this example demonstrates:
listOfWhiskies = whiskeyNames.Aggregate((accumulated, next) => { Console.Out.WriteLine("(Adding [{0}] to the list [{1}])", next, accumulated); return accumulated + " " + next; }); Console.Out.WriteLine(listOfWhiskies);
This outputs:
(Adding [Glenmorangie] to the list [Ardbeg 1998]) (Adding [Talisker] to the list [Ardbeg 1998 Glenmorangie]) (Adding [Cragganmore] to the list [Ardbeg 1998 Glenmorangie Talisker]) Ardbeg 1998 Glenmorangie Talisker Cragganmore
Finding the best item in a list
Whiskey mostExpensiveWhiskey = whiskies.Aggregate((champion, challenger) => challenger.Price > champion.Price ? challenger : champion); Console.WriteLine("Most expensive is {0}", mostExpensiveWhiskey.Name);
Creating a new ‘aggregated’ object
var blendedWhiskey = whiskies.Where(x=> x.Country == "Scotland") .Aggregate(new Whiskey() { Name="Tesco value whiskey", Age=3, Country="Scotland" }, (newWhiskey, nextWhiskey) => { newWhiskey.Ingredients.Add(nextWhiskey); newWhiskey.Price += (nextWhiskey.Price / 10); return newWhiskey; });
Alternative to Cunt
// 0 is the seed, and for each item, we effectively increment the current value.
// In this case we can ignore "item" itself.
int count = sequence.Aggregate(0, (current, item) => current + 1);
Summing numbers
var nums = new[]{1,2,3,4};
var sum = nums.Aggregate( (a,b) => a + b);
Console.WriteLine(sum); // output: 10 (1+2+3+4)
Note:
the aggregate result type an seed type is the same. You can use lambda expresion or lambda statement(need return) as accumulate function.
Reference:
1. Refactoring to LINQ Part 2: Aggregate is Great
2. LINQ Aggregate algorithm explained