0

I have this object structure:

public class Root
{
public int Value1;
public int Value2;
public List<NestedA> NestedAList;
}

public class NestedA
{
public List<NestedB> NestedBList;
public List<NestedC> NestedCList;
}

public class NestedB{
 public int ValueB;
 public int ValueB2;
}

public class NestedC{
 public int ValueC;
 public int ValueC2;
}

I need to group root objects using all Values from Root class and it's nested lists. I've been playing around a while and can't figure out how to/or if I can do this in a single group by statement, or what the best approach to acomplish this could be.

Edit: I need the items grouped by Root properties, Nested A Properties, Nested B Properties and Nested C Properties. So it makes sense: My real objects have more properties, just showing the ones that I need grouped, and can use as a start point.

Thanks in advance.

If we have this element

Root
Value1 = 1
Value2 = 2
NestedAList = [
    {NestedBList = [
        {ValueB=2, ValueB2=3}
    ]
    NestedCList = [
        {ValueC=5, ValueC2=11}
    ]}
]

it should be grouped with this one:

Root
Value1 = 1
Value2 = 2
NestedAList = [
    {NestedBList = [
        {ValueB=2, ValueB2=3}
    ]
    NestedCList = [
        {ValueC=5, ValueC2=11}
    ]}
]

but not with this one:

Root
Value1 = 1
Value2 = 2
NestedAList = [
    {NestedBList = [
        {ValueB=2, ValueB2=3}, { ValueB= 1, ValueB2=4}
    ]
    NestedCList = [
        {ValueC=5, ValueC2=11}
    ]}
]
Mg.
  • 1,469
  • 2
  • 16
  • 29
  • 4
    can you please show the simple input and output? you want to group by which property exactly? do you want to unwrap nested lists? its not clear. – M.kazem Akhgary Nov 19 '15 at 18:49
  • I suggest you check out this question. Maybe it's what you need.. http://stackoverflow.com/questions/5231845/c-sharp-linq-group-by-on-multiple-columns – Rodrigo Vedovato Nov 19 '15 at 19:04
  • That one is using multiple columns and selecting the group into a new one, I need to group using the list values. I did try grouping using some ideas from there however, couldn't make it work. – Mg. Nov 19 '15 at 19:10

1 Answers1

2

To accomplish this task, you can override Equals() and GetHashCode() methods for each class in your hierarchy. It may be little tricky, for example, like this:

public class Root
{
    public int Value1;
    public int Value2;
    public List<NestedA> NestedAList;

    public override bool Equals(object obj)
    {
        Root other = obj as Root;
        if (other == null) return false;
        return this.Value1 == other.Value1 && this.Value2 == other.Value2 && this.NestedAList.SequenceEqual(other.NestedAList);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            int hasha = 19;
            foreach (NestedA na in NestedAList)
            {
                hasha = hasha * 31 + na.GetHashCode();
            }

            return (Value1 ^ Value1 ^ hasha).GetHashCode();
        }               
    }
}

public class NestedA
{
    public List<NestedB> NestedBList;
    public List<NestedC> NestedCList;

    public override bool Equals(object obj)
    {
        NestedA other = obj as NestedA;
        if (other == null) return false;

        return NestedBList.SequenceEqual(other.NestedBList) && NestedCList.SequenceEqual(other.NestedCList);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            int hashb = 19;
            foreach (NestedB nb in NestedBList)
            {
                hashb = hashb * 31 + nb.GetHashCode();
            }
            int hashc = 19;
            foreach (NestedC nc in NestedCList)
            {
                hashc = hashc * 31 + nc.GetHashCode();
            }
            return (hashb ^ hashc).GetHashCode();
        }            
    }
}

public class NestedB{
     public int ValueB;
     public int ValueB2;

     public override bool Equals(object obj)
     {
         NestedB other = obj as NestedB;
         if (other == null) return false;
         return this.ValueB == other.ValueB && this.ValueB2 == other.ValueB2;
     }

     public override int GetHashCode()
     {
         return (ValueB ^ ValueB2).GetHashCode();
     }
}

public class NestedC{
     public int ValueC;
     public int ValueC2;

     public override bool Equals(object obj)
     {
         NestedC other = obj as NestedC;
         if (other == null) return false;
         return this.ValueC == other.ValueC && this.ValueC2 == other.ValueC2;
     }

     public override int GetHashCode()
     {
         return (ValueC ^ ValueC2).GetHashCode();
     }
}

After that you can easily select unique Roots (each unique Root represents a group):

roots.Distinct().ToList()

Same result using GoupBy():

roots.GroupBy(r => r).Select(g => g.First()).ToList()

Count elements in each group:

roots.GroupBy(r => r).Select(g => g.Count())

Enumerate elements in the first group:

roots.GroupBy(r => r).First().Select(g => g)

If you don't care about elements order in Lists, use Enumerable.All instead of SequenceEqual

EDIT: Also, in this case you have to change hash code generation alghoritm. For example, like this: hashb = hashb + nb.GetHashCode() * 31; (additional info about possible algorithms here)

Community
  • 1
  • 1
Igor Bendrup
  • 2,637
  • 2
  • 16
  • 15
  • I was going this way right now, will try this solution and let you know. – Mg. Nov 20 '15 at 12:41
  • Thanks this made it. Had to change hash code generation as I didn't care about elements order in list so I ended generating the codes this way eg: hashb = hashb + nb.GetHashCode() * 31;. – Mg. Nov 20 '15 at 15:54
  • You are welcome. I've updated my answer in according to your comment – Igor Bendrup Nov 21 '15 at 10:39