Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LINQ - groupBy with items in several Group

Tags:

c#

linq

I have a list of object that has two properties, let's say a and b. a is an Enum like:

[Flags] enum MyEnum
{
    first  = 1,
    second = 2,
    third  = 4,
    fourth = 8
};

b is an unsigned integer, which is a mask (combination of the MyEnum flags).

now i need to sum every object by their a - meaning an object can be summed twice if obj.a = first | third and i can't seem to do a groupBy to them. is there a different way to sum them?

I'm sorry i don't share my code, but i can't. i can tell you i did it using foreach in just some if - else blocks but i think i should learn how to do it in Linq

EDIT: I think i wasn't clear. i want to sum the object by the Enum, meaning if i have:

obj1.a = first, obj1.b = 5
obj2.a = first | second, obj2.b = 3

then the output will be

first sum = 8
second sum = 3
like image 829
No Idea For Name Avatar asked Aug 02 '13 09:08

No Idea For Name


2 Answers

Given a MyEnum and a MyClass

[Flags]
enum MyEnum
{
    first = 1,
    second = 2,
    third = 4,
    forth = 8
}

class MyClass
{
    public MyEnum MyEnum;
    public uint Value;
}

And some values

var mcs = new[] { 
    new MyClass { MyEnum = MyEnum.first | MyEnum.third, Value = 10 },
    new MyClass { MyEnum = MyEnum.second, Value = 20 },
    new MyClass { MyEnum = MyEnum.first, Value = 100 },
};

This LINQ expression will return for each value of the enum the sum of the values.

var ret = from p in Enum.GetValues(typeof(MyEnum)).Cast<MyEnum>() 
              select new { MyEnum = p, Sum = mcs.Where(q => q.MyEnum.HasFlag(p)).Sum(q => q.Value) };

Note that it will return a "row" even for MyEnum.fourth with value 0.

The expression starts with the values of the enum (Enum.GetValues(typeof(MyEnum)).Cast<MyEnum>()) and then for each value it sums the values of the mcs that have the same MyEnum (mcs.Where(q => q.MyEnum.HasFlag(p)).Sum(q => q.Value))

If you want to exclude the values of the enum that aren't used:

var ret = from p in Enum.GetValues(typeof(MyEnum)).Cast<MyEnum>()
          let temp = mcs.Where(q => q.MyEnum.HasFlag(p)).Select(q => q.Value).ToArray()
          where temp.Length > 0
          select new { MyEnum = p, Sum = temp.Sum(q => q) };

The expression starts with the values of the enum (Enum.GetValues(typeof(MyEnum)).Cast<MyEnum>()) and then for each value it "saves" the values of the mcs that have the same MyEnum (let temp = mcs.Where(q => q.MyEnum.HasFlag(p)).Select(q => q.Value).ToArray()) in temp, skips the temp that are empty (where temp.Length > 0) and sum the remaining temp (select new { MyEnum = p, Sum = temp.Sum(q => q) }). Note that if you use an uint you have to use temp.Sum(q => q), but with an int you can use temp.Sum() (or you can use temp.Sum(q => q)).

Another way is through a double from and a group by

var ret = from p in Enum.GetValues(typeof(MyEnum)).Cast<MyEnum>()
          from q in mcs 
          where q.MyEnum.HasFlag(p)
          group q.Value by p into r
          select new { MyEnum = r.Key, Sum = r.Sum(p => p) };

It's probably equivalent to using the SelectMany as suggested by ChaseMedallion (a double from is converted by the compiler to a SelectMany)

like image 198
xanatos Avatar answered Sep 20 '22 01:09

xanatos


Try using HasFlag and GetValues to extract all the flags for a particular enum value:

var objs = new[] { new { a = MyEnum.first | MyEnum.second }, new { a = MyEnum.first }... };
var enumValues = Enum.GetValues(typeof(MyEnum)).Cast<MyEnum>().ToArray();

// first, use SelectMany to create a sequence with one instance of each object for
// each flag value in it's a
var grouped = objs.SelectMany(
        o => enumValues.Where(flag => o.a.HasFlag(flag))
            .Select(v => new { o, flag })
    )
    // then group by the extracted flag value
    // the element selector (t => t.o) gets us back to an IGrouping of o's (dropping the extra flag property
    .GroupBy(t => t.flag, t => t.o);

 // finally, we can get sums by a as follows:
 var sumsByA = grouped.ToDictionary(g => g.Key, g => g.Sum(o => o.b));
like image 33
ChaseMedallion Avatar answered Sep 19 '22 01:09

ChaseMedallion