views:

536

answers:

3

I've seen the simple example of the .net Aggregate function working like so:

string[] words = { "one", "two", "three" };
var res = words.Aggregate((current, next) => current + ", " + next);
Console.WriteLine(res);

How could the 'Aggregate' function be used if you wish to aggregate more complex types? For example: a class with 2 properties such as 'key' and 'value' and you want the output like this:

"MyAge: 33, MyHeight: 1.75, MyWeight:90"
+3  A: 

The Aggregate function accepts a delegate parameter. You define the behavior you want by changing the delegate.

var res = data.Aggregate((current, next) => current + ", " + next.Key + ": " + next.Value);
Joel Coehoorn
If you're aggregating into a type other than the source you need to specify a seed. Specifying "" as a seed would compile, but the result will start with ", ".
dahlbyk
+1  A: 

Aggregate has 3 overloads, so you could use the one that has different type to accumulate the items you are enumerating.

You would need to pass in a seed value (your custom class), and a method to add merge the seed with one value. Example:

MyObj[] vals = new [] { new MyObj(1,100), new MyObj(2,200), ... };
MySum result = vals.Aggregate<MyObj, MySum>(new MySum(),
    (sum, val) =>
    {
       sum.Sum1 += val.V1;
       sum.Sum2 += val.V2;
       return sum;
    }
Yurik
+7  A: 

You have two options:

  1. Project to a string and then aggregate:

    var values = new[] {
        new { Key = "MyAge", Value = 33.0 },
        new { Key = "MyHeight", Value = 1.75 },
        new { Key = "MyWeight", Value = 90.0 }
    };
    var res1 = values.Select(x => string.Format("{0}:{1}", x.Key, x.Value))
                    .Aggregate((current, next) => current + ", " + next);
    Console.WriteLine(res1);
    

    This has the advantage of using the first string element as the seed (no prepended ", "), but will consume more memory for the strings created in the process.

  2. Use an aggregate overload that accepts a seed, perhaps a StringBuilder:

    var res2 = values.Aggregate(new StringBuilder(),
        (current, next) => current.AppendFormat(", {0}:{1}", next.Key, next.Value),
        sb => sb.Length > 2 ? sb.Remove(0, 2).ToString() : "");
    Console.WriteLine(res2);
    

    The second delegate converts our StringBuilder into a string, using the conditional to trim the starting ", ".

dahlbyk
Perfect, just what I was looking for, so I could time them all, from my timings, rolling your own for loop was heaps quicker (I only have 1 or 2 items in my test tho)
Myster
With so few items in the list, the performance hit for setting up the "extra" enumerators for Select/Aggregate will seem pretty severe compared with a more imperative solution. As with most functional solutions, the question is if the performance/readability trade-off is acceptable. Given how unfamiliar Aggregate would be to most people, it would be easy to conclude that in this case the imperative solution is "better" independent of performance.
dahlbyk
After having written it both ways, I agree. The Aggregate is pretty cryptic, but it's a good tool to have in my box ;-)
Myster