tags:

views:

2221

answers:

5

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

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

Marc Gravell
Yeah, that is kind of my backup path. But I would still like to learn how that Aggregate method really works. Cause it annoys me that I can't seem to use it successfully :p
Svish
And yes, let's keep that number forever secret. Never ever think about posting any sort of statistics like that here on SO :p (Although I must say my curiosity kind of wants it... but keep it private in that case!)
Svish
+5  A: 
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);
Daniel Brückner
Initialize with TimeSpan.Zero. Very good point. This was with a list of TimeSpans though, I needed a list of something else with a TimeSpan property. But I figured it out. Thanks!
Svish
+3  A: 
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
Chris Doggett
This was with ints, which I already had kind of solved. But thanks for clearing up how to initialize it!
Svish
Sorry it was originally using ints. I misread the question, but this should work for you, as well as explain how Aggregate works in general.
Chris Doggett
A: 

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!

Svish
A: 

You could sum on one of the Total properties of the TimeSpan. For instance, you could get the represented TotalHours of time spent on SO like this:

g.Sum(x => x.SOReputation.TotalHours)

I believe this would give you the result you're looking for, but with the caveat that you'd have to put the units of measure according to what you need (hours, minutes, second, days, etc.)

Joseph
Actually this is what I ended up doing, but for other reasons. Still happy that I finally understand the Aggregate method (Although not an expert :p).
Svish