Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C#: How to use the Enumerable.Aggregate method

Tags:

c#

time

aggregate

Lets say I have this amputated Person class:

class Person
{
    public int Age { get; set; }
    public string Country { get; set; }

    public int SOReputation { get; set; }
    public TimeSpan TimeSpentOnSO { get; set; }

    ...
}

I can then group on Age and Country like this:

    var groups = aListOfPeople.GroupBy(x => new { x.Country, x.Age });

Then I can output all the groups with their reputation totals like this:

foreach(var g in groups)
    Console.WriteLine("{0}, {1}:{2}", 
        g.Key.Country, 
        g.Key.Age, 
        g.Sum(x => x.SOReputation));

My question is, how can I get a sum of the TimeSpentOnSO property? The Sum method won't work in this case since it is only for int and such. I thought I could use the Aggregate method, but just seriously can't figure out how to use it... I'm trying all kinds properties and types in various combinations but the compiler just won't recognize it.

foreach(var g in groups)
    Console.WriteLine("{0}, {1}:{2}", 
        g.Key.Country, 
        g.Key.Age, 
        g.Aggregate(  what goes here??  ));

Have I completely missunderstood the Aggregate method? Or what is going on? Is it some other method I should use instead? Or do I have to write my own Sum variant for TimeSpans?

And to add to the mess, what if Person is an anonymous class, a result from for example a Select or a GroupJoin statement?


Just figured out that I could make the Aggregate method work if I did a Select on the TimeSpan property first... but I find that kind of annoying... Still don't feel I understand this method at all...

foreach(var g in groups)
    Console.WriteLine("{0}, {1}:{2}", 
        g.Key.Country, 
        g.Key.Age, 
        g.Select(x => x.TimeSpentOnSO)
        g.Aggregate((sum, x) => sum + y));
like image 830
Svish Avatar asked Jun 09 '09 13:06

Svish


4 Answers

List<TimeSpan> list = new List<TimeSpan>
    {
        new TimeSpan(1),
        new TimeSpan(2),
        new TimeSpan(3)
    };

TimeSpan total = list.Aggregate(TimeSpan.Zero, (sum, value) => sum.Add(value));

Debug.Assert(total.Ticks == 6);
like image 172
Daniel Brückner Avatar answered Sep 26 '22 18:09

Daniel Brückner


g.Aggregate(TimeSpan.Zero, (i, p) => i + p.TimeSpentOnSO)

Basically, the first argument to Aggregate is an initializer, which is used as the first value of "i" in the function passed in the second argument. It'll iterate over the list, and each time, "i" will contain the total so far.

For example:

List<int> nums = new List<int>{1,2,3,4,5};

nums.Aggregate(0, (x,y) => x + y); // sums up the numbers, starting with 0 => 15
nums.Aggregate(0, (x,y) => x * y); // multiplies the numbers, starting with 0 => 0, because anything multiplied by 0 is 0
nums.Aggregate(1, (x,y) => x * y); // multiplies the numbers, starting with 1 => 120
like image 31
Chris Doggett Avatar answered Sep 28 '22 18:09

Chris Doggett


A combination of Chris and Daniels answers solved it for me. I needed to initialize the TimeSpan, and I did things in the wrong order. The solution is:

foreach(var g in groups)
    Console.WriteLine("{0}, {1}:{2}", 
        g.Key.Country, 
        g.Key.Age, 
        g.Aggregate(TimeSpan.Zero, (sum, x) => sum + x.TimeSpentOnSO));

Thanks!

And also... D'oh!

like image 41
Svish Avatar answered Sep 28 '22 18:09

Svish


You could write TimeSpan Sum method...

public static TimeSpan Sum(this IEnumerable<TimeSpan> times)
{
    return TimeSpan.FromTicks(times.Sum(t => t.Ticks));
}
public static TimeSpan Sum<TSource>(this IEnumerable<TSource> source,
    Func<TSource, TimeSpan> selector)
{
    return TimeSpan.FromTicks(source.Sum(t => selector(t).Ticks));
}

Alternatively, MiscUtil has generic-enabled Sum methods, so Sum should work on a TimeSpan just fine (since there is a TimeSpan+TimeSpan=>TimeSpan operator defined).

Just please don't tell me the number... it would scare me...

like image 1
Marc Gravell Avatar answered Sep 27 '22 18:09

Marc Gravell