tags:

views:

99

answers:

4

What is the easiest and somewhat efficient way to convert a flat structure:

object[][] rawData = new object[][] 
{ 
  { "A1", "B1", "C1" }, 
  { "A1", "B1", "C2" },
  { "A2", "B2", "C3" }, 
  { "A2", "B2", "C4" }
  // .. more 
};

into a hierarchical structure:

class X
{
  public X () 
  {
    Cs = new List<string>();
  }
  public string A { get; set; }
  public string B { get; set; }
  public List<string> Cs { get; private set; }
}

the result should look like this

// pseudo code which describes structure:
result =
{
  new X() { A = "A1", B = "B1", Cs = { "C1", "C2" } },
  new X() { A = "A2", B = "B2", Cs = { "C3", "C4" } }
}

Preferably using Linq extension methods. Target class X could be changed (eg. a public setter for the List), only if not possible / useful as it is now.

+2  A: 

for this particular case:

   .GroupBy( x => new { a = x[0], b = x[1] } )
   .Select( x => new { A = x.Key.a, B = x.Key.b, C = x.Select( c => c[2] ) })
Andrey
for his example hierarchy:`.Select( x => new X { A = x.Key.a, B = x.Key.b, Cs = x.Select( c => c[2] ).ToList() })`
Eamon Nerbonne
I'm confused because of the C = x.Select ... Does this really work like this? Have to try.
Stefan Steinegger
Yes, it works, it's actually basic usage of `GroupBy`. Thanks a lot.
Stefan Steinegger
A: 

Something like this should work if the depth of your hierarchy is limited (as in your example where you have only three levels A, B and C). I simplified your X a little bit:

class X {
    public string A { get; set; }
    public string B { get; set; }
    public List<string> Cs { get; set; }
} 

Then you can use nested GroupBy as many times as you need (depending on the depth of the hierarchy). It would be also relatively easy to rewrite this into a recursive method (that would work for arbitrarily deep hierarchies):

// Group by 'A'
rawData.GroupBy(aels => aels[0]).Select(a => 
  // Group by 'B'
  a.GroupBy(bels => bels[1]).Select(b =>
    // Generate result of type 'X' for the current grouping
    new X { A = a.Key, B = b.Key, 
            // Take the third element 
            Cs = b.Select(c => c[2]).ToList() }));

This is more explicit than the other solutions here, but maybe it will be more readable as it is more straightforward encoding of the idea...

Tomas Petricek
+1  A: 

With X members being strings and Cs being a private set, and rawData being an array of arrays of objects, I would add a constructor to X public X(string a, string b, List<string> cs) and then perform this code

var query = from row in rawData
            group row by new { A = row[0], B = row[1] } into rowgroup
            select new X((string)rowgroup.Key.A, (string)rowgroup.Key.B, rowgroup.Select(r => (string)r[2]).ToList());

This is on the following raw data

object[][] rawData = new object[][]  
    {  
        new object[] { "A1", "B1", "C1" },  
        new object[] { "A1", "B1", "C2" }, 
        new object[] { "A2", "B2", "C3" },  
        new object[] { "A2", "B2", "C4" } 
        // .. more  
    };
Anthony Pegram
Thanks. I implemented a constructor like this (but with a IEnumerable instead of the List, copying it into an array internally). The query also looks good, I'm just more familiar with the Extension methods. I accepted Andrey's answer because he was the first with the (basically) correct answer.
Stefan Steinegger
A: 

I wanted to see if I could write this without anonymous instances. It's not too bad:

IEnumerable<X> = rawData
  .GroupBy(raw => raw[1])
  .Select(g1 => g1.GroupBy(raw => raw[0]))
  .SelectMany(g0 => g0
    .Select(g1 => new X()
      {
        A = g0.Key,
        B = g1.Key,
        C = g1.Select(raw => raw[2]).ToList()
      }
    )
  );
David B
Interesting solution. You could also use a `KeyValuePair<K,V>` for only two keys.
Stefan Steinegger