Bart's Blog

Just another WordPress.com weblog

  • Categories

  • Archives

Pivot extension method for LINQ

Posted by bartmaes on March 2, 2010

A colleague of mine asked me how to pivot a table using LINQ. I didn’t want to cast the table to an array of arrays, so I came up with the following code:

public static class TableExtensions
{
 public static IEnumerable<IEnumerable> Pivot(this IEnumerable<IEnumerable> table)
 {
  IEnumerable first = table.FirstOrDefault();
  if (first != null)
  {
   List enumerators = new List();
   foreach (IEnumerable row in table)
   {
    enumerators.Add(row.GetEnumerator());
   }
   foreach (object element in first)
   {
    yield return GetColumn(enumerators);
   }
  }
 }

 private static IEnumerable GetColumn(IEnumerable enumerators)
 {
  foreach (IEnumerator enumerable in enumerators)
  {
   if (enumerable.MoveNext())
   {
    yield return enumerable.Current;
   }
  }
 }
}

You can see the result when testing with the following console application:

class Program
{
 private static object[][] _table = new object[][]
 {
  new object[]{11,12,13},
  new object[]{21,22,23},
  new object[]{31,32,33},
  new object[]{41,42,43}
 };

 static void Main(string[] args)
 {
  PrintTable(_table);
  PrintTable(_table.Pivot());
  Console.ReadLine();
 }

 private static void PrintTable(IEnumerable<IEnumerable> table)
 {
  foreach (var row in table)
  {
   foreach (object o in row)
   {
    Console.Write(o.ToString() + ” “);
   }
   Console.WriteLine();
  }
  Console.WriteLine();
 }
}

Now this code works fine as long as you nicely iterate over each of the columns in the correct order. You will notice the bug when doing the following:

PrintTable(_table.Skip(1));
PrintTable(_table.Pivot().Skip(1));
PrintTable(_table.Pivot().Pivot().Skip(1));

The reason for this bug is that the constructed enumerators are shared between all the “column enumerators” and their state is changed along the way. There is no efficient way to work around this problem when only using the IEnumerable interface (efficient in the sense of memory and time consumption), so you’ll have to settle with the next best thing, casting to an object array:

public static IEnumerable<IEnumerable> Pivot(this IEnumerable<object[]> table)
{
 object[] first = table.FirstOrDefault();
 if (first != null)
 {
  for (int i = 0; i < first.Count(); ++i)
  {
   yield return table.Select(row => row[i]);
  }
 }
}

Of course you can work with generics to get a strongly typed result, but sometimes the elements in every column can have another type. The tuple class in .NET 4.0 can help you with this. You just have to create a bunch of overloads like this:

public static Tuple<IEnumerable<T1>, IEnumerable<T2>, …> Pivot<T1, T2, …>(this IEnumerable<Tuple<T1, T2, …>> table)

2 Responses to “Pivot extension method for LINQ”

  1. Cliff said

    Well done Bartmeas,

    This is an excellent solution. I have been searching for days for pivot table solutions and this is by far the most elegant solution I have found. However, I have been trying to adapt it to other classes of data ( say a typical Customer class that contains say Name(string) and Age(int) properties) and I have been failing miserably. Any further help or suggestions with making your extension work Generically would be much appreciated.
    Thanks in advance,

    Cliff

    • bartmaes said

      Hello Cliff,

      In .NET 4.0, I think that the easiest solution is to allow converting the Customer to a Tuple (e.g. Tuple containing Name and Age). So you’ll get something like:

      IEnumerable customers=…;
      IEnumerable<Tuple> tuples = customers.Select(customer => Tuple.Create(customer.Name, customer.Age));
      Tuple<IEnumerable, IEnumerable> tuple = tuples.Pivot();

      (I haven’t installed Visual Studio 2010 yet, so I hope I didn’t make any mistakes.)

      In older version of .NET you cannot use the Tuple class and using IEnumerables in stead produces less readable code. You could create an extra extension method to help creating the “tuple”:

      public static IEnumerable ConvertToTuple(this Customer customer)
      {
       yield return customer.Name;
       yield return customer.Age;
      }

      Bart

Leave a comment