1

I have a class with multiple properties, of which I'm interested in two. Say, PropA and PropB in the following example.

public class GroupByOR
{
    public string PropA { get; set; }
    public string PropB { get; set; }
    public int SomeNumber { get; set; }
    public GroupByOR(string a, string b, int num) { PropA = a; PropB = b; SomeNumber = num; }
}

And I would like to group a list of this class' objects, criteria being an item should fall into a group if either PropA or PropB matches.

For example, let's say my list looks like this:

List<GroupByOR> list = new List<GroupByOR>
{
    new GroupByOR("CA", "NY", 1), // Item 1
    new GroupByOR("CA", "OR", 2), // Item 2
    new GroupByOR("NY", "OR", 5)  // Item 3
};

Then my desired outcome is this:

Group 1: Items 1 and 2 (based on CA)

Group 2: Items 1 and 3 (based on NY)

Group 3: Items 2 and 3 (based on OR)

Looking around, I found this and many other examples, but they all seem to focus on grouping by multiple properties, but with an AND operation.

Is what I'm trying to achieve even possible?

Or else is join the way to go here? If so can you please direct me in the right direction?

Community
  • 1
  • 1
Sach
  • 10,091
  • 8
  • 47
  • 84

1 Answers1

4

You're going to end up with more items than you started with, so GroupBy isn't the whole answer. You need something like this:

List<GroupByOR> list = new List<GroupByOR>
{
    new GroupByOR("CA", "NY", 1), // Item 1
    new GroupByOR("CA", "OR", 2), // Item 2
    new GroupByOR("NY", "OR", 5)  // Item 3
};

var lookupA = list.ToLookup(e => e.PropA);
var lookupB = list.ToLookup(e => e.PropB);
var keys = lookupA.Select(e => e.Key)
    .Concat(lookupB.Select(e => e.Key)).Distinct();

var result = keys.Select(e => 
    new 
    { 
        Key = e, 
        Values = lookupA[e].Concat(lookupB[e]).Distinct()
    });

This is projecting the result into a new anonymous type with Key being the value of PropA or PropB and Values being an IEnumerable<GroupByOr> of all the matching elements.

Edit: Code walkthrough

The first two linq lines are making a lookup (effectively a multi-valued dictionary) out of the enumeration, with the given key. These can be enumerated as IEnumerable<IGrouping<TKey, TValue>>, but can also be used for efficient lookups.

var lookupA = list.ToLookup(e => e.PropA);
var lookupB = list.ToLookup(e => e.PropB);

The next line is finding all the distinct values of PropA and PropB (using the lookups, but could have gone back to the list for this too).

var keys = lookupA.Select(e => e.Key).Concat(lookupB.Select(e => e.Key)).Distinct();

The last line is taking the distinct keys, and taking the matching enumerations from both the propA lookup and the propB lookup then concatenating them, and (now I've spotted another bug) de-duplicating them.

The select statement is producing an anonymous type - these types can't be referred to explicitly, but then can be assigned to a var and they can be enumerated. If you want to store the resulting value, you'd have to make a non-anonymous type.

var result = keys.Select(e => 
    new 
    { 
        Key = e, 
        Values = lookupA[e].Concat(lookupB[e]).Distinct()
    });

Edit: Output

CA
    Values: (PropA: CA, PropB: NY, SomeNumber: 1) (PropA: CA, PropB: OR, SomeNumber: 2)
NY
    Values: (PropA: NY, PropB: OR, SomeNumber: 5) (PropA: CA, PropB: NY, SomeNumber: 1)
OR
    Values: (PropA: CA, PropB: OR, SomeNumber: 2) (PropA: NY, PropB: OR, SomeNumber: 5)
Adam Brown
  • 1,667
  • 7
  • 9
  • Adam, thanks for the reply. But does this work quite correctly? This groups into ('groups' may not be the correct word) two items, first with Key=CA and the second with Key=NY, and both have additional items. Please see the image here: https://imgur.com/kWuXb8v – Sach Jan 12 '18 at 01:14
  • Sorry. This was wrong. Fixed now. (Perils of writing code that you don't run) - once I ran it, it was obvious the mistake I'd made - I missed the keys that only occurred in PropB. – Adam Brown Jan 12 '18 at 01:17
  • OK that's exactly what I needed, thanks! Can you please explain the last two lines a little bit more? I'm not very proficient in LINQ so that'll be useful. – Sach Jan 12 '18 at 01:23
  • Specially the last line where you do a `keys.Select()`. Thanks! – Sach Jan 12 '18 at 01:24
  • 1
    Turns out I missed something important. You would have ended up with two of the same objects in the same grouping if you had a repeated value for PropA and PropB. That is now fixed. – Adam Brown Jan 12 '18 at 01:30
  • Well actually I didn't give a full example but my list is allowed to have repeated values. So actually what I want is the previous version without the last `Distinct`. But that's a minor issue and I can fix it. Thanks for the detailed explanation! – Sach Jan 12 '18 at 01:32