3

I am confused by the groupby behavior in LINQ to objects. Lets assume for this I have the following class;

public class person
{
   public string name { get; set; }
   public int age { get; set; }
}

Lets say I have a list or type person; List<person> people

Now, I want to produce an IEnumerable<T> with an IGrouping or anonymous type that has two properties 1) the name (the key) and 2) the sum of all of the ages for people with that name.

Here are a couple of examples of things I've tried (unsuccessfully);

people.GroupBy(x => x.name, x => x, (key, value) => value.Aggregate((c, n) => c + n));

That won't compile with the error cannot convert type "int" to "namespace.person"

Before that I was trying something more along the lines of;

people.GroupBy(x => x.name).Select(g => new { g.Key, g.Aggregate((c, n) => c + n)) } );

Which essentially gives the same error. I'm basically struggling to understand what the value returned by GroupBy really is. At first I thought that basic overload was giving me a key value pair where x.Key was the key I specified with my delegate and x.Value would be an IEnumerable<T> where the typeof T would be the type of x. Of course, if that were the case my second example would work like a charm. My question is somewhat open ended but can someone explain 2 things, firstly; How do I accomplish my end goal with LINQ? And Secondly, why isn't the result of GroupBy more along the lines of what I describe here? What is it? I feel like a key value pair where the value is a collection of objects that match that key is far more intuitive than what is actually returned.

evanmcdonnal
  • 46,131
  • 16
  • 104
  • 115

2 Answers2

6
 var grouped = people.GroupBy(x => x.name)
                                .Select(x => new
                                    {
                                        Name = x.Key,
                                        Age = x.Sum(v => v.age),
                                        Result = g.Aggregate(new Int32(), (current, next) => next.age + next.age)
                                    });

If you want you can group the result of that again by Name and it will be a grouping with Key as the name and Age as the value

Honorable Chow
  • 3,097
  • 3
  • 22
  • 22
  • So I up voted because that satisfied my theoretical example, but what if I actually have to use `Aggregate` to achieve the results I'm looking for? Pretend the type is more complicated and I can't just supply a simple projection for what to sum (need something far more proprietary dealing with multiple properties on an object say). – evanmcdonnal Apr 17 '14 at 21:20
  • I think the issue is you are missing the return type of the aggregate. Since you didn't specify it, it's expecting your result to be of type person. The two sides need to match. Now depending on what you are trying to return and what other classes/assemblies are involved you may not be able to do the aggregate until after you have resolved the query with ToList(). I added this above; Result = g.Aggregate(new Int32(), (current, next) => next.age + next.age) – Honorable Chow Apr 17 '14 at 22:22
  • That use of Aggregate doesn't quite work although it gets closer to what I'm looking for. If you have for example a single element it would return `age * 2` when it should just return `age * 1`. I think it works for other cases though I'm not positive yet. – evanmcdonnal Apr 17 '14 at 23:29
  • I don't have to good a vision of what you're needing. You do still have access to x in the Aggregate if you need to get x.Count or something. – Honorable Chow Apr 17 '14 at 23:37
  • 1
    That use of aggregate isn't actually right. It doesn't give you the sum of the ages. I need to use aggregate because my data model is more complicated than in my example. I can't just call `Sum(x => x.SingleProperty)`. I could edit the example to make it better demonstrate the operation that I'm actually doing however I don't think it matters.`next.age + next.age` this isn't right and from what I saw in the debugger I believe `current` refers to the return value of `new Int32()` which prevents that from actually yielding the correct result. – evanmcdonnal Apr 18 '14 at 01:35
  • Based on this I determined you're providing a seed value http://stackoverflow.com/questions/7105505/linq-aggregate-algorithm-explained the problem is that `Int32` doesn't contain a definition for the properties so you can't reference `current`. If you do `next` in both cases then in the case of a singleton it returns `0 + next.age + next.age` so you get the wrong total. That doesn't quite work when your lambda is more complicated. – evanmcdonnal Apr 18 '14 at 01:41
  • If you spell out in more detail what you are trying to do I'll happily take a look. I'm not convinced you need aggregate at all. I've done all kinds of complex computations using linq and I never use Aggregate. The only time I use aggregate is when I have the equivilant of a for loop building something, like this with StringBuilder; .Aggregate(new StringBuilder(), (current, next) => current.Append(next).Append(", ")). Allows me to create a comma separated list. – Honorable Chow Apr 18 '14 at 02:17
  • 1
    Where does the "g" parameter come from? It is nowhere defined in your example. – Paul Chernoch Apr 25 '18 at 15:09
0

you can do it with expression syntax

var results = from p in persons
              group p.car by p.name into g
              select new { name = g.Key, age = g.Sum(c=>.age };
COLD TOLD
  • 13,513
  • 3
  • 35
  • 52