32

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"

4 Answers 4

62

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 ", ".

Sign up to request clarification or add additional context in comments.

5 Comments

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)
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.
After having written it both ways, I agree. The Aggregate is pretty cryptic, but it's a good tool to have in my box ;-)
why not var res1 = String.Join(",",values.Select(x => string.Format("{0}:{1}", x.Key, x.Value)));
@Sean: Because that overload of String.Join() didn't exist until .NET 4.
6

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;
    }

Comments

4

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);

1 Comment

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 ", ".
0

Or use string.Join() :

var values = new[] {
    new { Key = "MyAge", Value = 33.0 },
    new { Key = "MyHeight", Value = 1.75 },
    new { Key = "MyWeight", Value = 90.0 }
};
var res = string.Join(", ", values.Select(item => $"{item.Key}: {item.Value}"));
Console.WriteLine(res);

1 Comment

This code works perfectly, but doesn't answer the question about Aggregate.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.