views:

967

answers:

4

I am trying to see if I can use LINQ to solve a problem I am having. I have a collection of items that contain an Enum (TypeCode) and a User object, and I need to flatten it out to show in a grid. It's hard to explain, so let me show a quick example.

Collection has items like so:
TypeCode | User

1 | Don Smith
1 | Mike Jones
1 | James Ray
2 | Tom Rizzo
2 | Alex Homes
3 | Andy Bates

I need the output to be:
1 | 2 | 3
Don Smith | Tom Rizzo | Andy Bates
Mike Jones | Alex Homes |
James Ray | |

Thanks to anyone who can help me! I've tried doing this using foreach, but I can't do it that way because I'd be inserting new items to the collection in the foreach, causing an error.

A: 

You can use Linq's .ToLookup to group in the manner you are looking for.

var lookup = data.ToLookup(d => d.TypeCode, d => d.User);

Then it's a matter of putting it into a form that your consumer can make sense of. For instance:

//Warning: untested code
var enumerators = lookup.Select(g => g.GetEnumerator()).ToList();
int columns = enumerators.Count;
while(columns > 0)
{
  for(int i = 0; i < enumerators.Count; ++i)
  {
    var enumerator = enumerators[i];
    if(enumator == null) continue;
    if(!enumerator.MoveNext())
    { 
      --columns;
      enumerators[i] = null;
    }
  }
  yield return enumerators.Select(e => (e != null) ? e.Current : null);
}

Put that in an IEnumerable<> method and it will (probably) return a collection (rows) of collections (column) of User where a null is put in a column that has no data.

Talljoe
+2  A: 

I'm not saying it is a great way to pivot - but it is a pivot...

    // sample data
    var data = new[] {
        new { Foo = 1, Bar = "Don Smith"},
        new { Foo = 1, Bar = "Mike Jones"},
        new { Foo = 1, Bar = "James Ray"},
        new { Foo = 2, Bar = "Tom Rizzo"},
        new { Foo = 2, Bar = "Alex Homes"},
        new { Foo = 3, Bar = "Andy Bates"},
    };
    // group into columns, and select the rows per column
    var grps = from d in data
              group d by d.Foo
              into grp
              select new {
                  Foo = grp.Key,
                  Bars = grp.Select(d2 => d2.Bar).ToArray()
              };

    // find the total number of (data) rows
    int rows = grps.Max(grp => grp.Bars.Length);

    // output columns
    foreach (var grp in grps) {
        Console.Write(grp.Foo + "\t");
    }
    Console.WriteLine();
    // output data
    for (int i = 0; i < rows; i++) {
        foreach (var grp in grps) {
            Console.Write((i < grp.Bars.Length ? grp.Bars[i] : null) + "\t");
        }
        Console.WriteLine();
    }
Marc Gravell
A: 

I guess this is similar to Marc's answer, but I'll post it since I spent some time working on it. The results are separated by " | " as in your example. It also uses the IGrouping<int, string> type returned from the LINQ query when using a group by instead of constructing a new anonymous type. This is tested, working code.

var Items = new[] {
    new { TypeCode = 1, UserName = "Don Smith"},
    new { TypeCode = 1, UserName = "Mike Jones"},
    new { TypeCode = 1, UserName = "James Ray"},
    new { TypeCode = 2, UserName = "Tom Rizzo"},
    new { TypeCode = 2, UserName = "Alex Homes"},
    new { TypeCode = 3, UserName = "Andy Bates"}
};
var Columns = from i in Items
              group i.UserName by i.TypeCode;
Dictionary<int, List<string>> Rows = new Dictionary<int, List<string>>();
int RowCount = Columns.Max(g => g.Count());
for (int i = 0; i <= RowCount; i++) // Row 0 is the header row.
{
    Rows.Add(i, new List<string>());
}
int RowIndex;
foreach (IGrouping<int, string> c in Columns)
{
    Rows[0].Add(c.Key.ToString());
    RowIndex = 1;
    foreach (string user in c)
    {
        Rows[RowIndex].Add(user);
        RowIndex++;
    }
    for (int r = RowIndex; r <= Columns.Count(); r++)
    {
        Rows[r].Add(string.Empty);
    }
}
foreach (List<string> row in Rows.Values)
{
    Console.WriteLine(row.Aggregate((current, next) => current + " | " + next));
}
Console.ReadLine();

I also tested it with this input:

var Items = new[] {
    new { TypeCode = 1, UserName = "Don Smith"},
    new { TypeCode = 3, UserName = "Mike Jones"},
    new { TypeCode = 3, UserName = "James Ray"},
    new { TypeCode = 2, UserName = "Tom Rizzo"},
    new { TypeCode = 2, UserName = "Alex Homes"},
    new { TypeCode = 3, UserName = "Andy Bates"}
};

Which produced the following results showing that the first column doesn't need to contain the longest list. You could use OrderBy to get the columns ordered by TypeCode if needed.

1 | 3 | 2
Don Smith | Mike Jones | Tom Rizzo
 | James Ray | Alex Homes
 | Andy Bates |
Dennis Palmer
A: 

Check this out http://www.extensionmethod.net/Details.aspx?ID=147 Hope this helps

Vasu Balakrishnan