Hemme's Blog

Stories from behind the keyboard

  • RSS
  • Twitter
  • Linkedin

The Iterator

In the article Closing the Gap Between IEnumerator and IEnumerable, Henon describes the class Iterator and its generic version Iterator<T>. An Iterator is basically a wrapper around an IEnumerator, and exposes an IEnumerable interface. This way you can easily apply IEnumerable extension methods to any object that implements GetEnumerator() - such as Sum(), Min(), and so on...

The following code is an implementation of a generic Iterator<T>, slightly adapted from the implementation described in the article above-mentioned:

public class Iterator<T> : IEnumerable<T>
{
  readonly IEnumerator<T> _iterator;

  public Iterator(IEnumerator<T> enumerator)
  {
    _iterator = enumerator;
  }

  public Iterator(IEnumerable<T> enumerable)
  {
    _iterator = enumerable.GetEnumerator();
  }

  public IEnumerator<T> GetEnumerator()
  {
    return _iterator;
  }

  IEnumerator IEnumerable.GetEnumerator()
  {
    return _iterator;
  }
}

Visiting n-dimensional arrays along a defined dimension

DotNet n-dimensional arrays implement GetEnumerator() but they don't implement any method to visit their elements along a defined dimension (e.g. by rows or by columns..); luckily enough, the Iterator<T> can be used over n-dimensional arrays to solve the problem.

For the sake of simplicity, let's consider the basic case of a two-dimensional array. Since Array implements GetEnumerator() and every T[,] is an Array, let's write an adapter to recover an IEnumerable<T> from Array.GetEnumerator().

public static class EnumeratorExtensions
{
//Adapted from Jon Skeet, at StackOverflow http://stackoverflow.com/questions/828342/what-is-the-best-way-to-convert-an-ienumerator-to-a-generic-ienumerator   
  public static IEnumerator<T> AsEnumerator<T>(this IEnumerator e)
  {
   while (e.MoveNext())
     yield return (T) e.Current;
  }
}

As a consequence, you can grab an IEnumerable<T> from a T[,] array in two simple steps:

int[,] numbers;

//Initialization code goes here...

var typedEnumerator = numbers.GetEnumerator().AsEnumerable<int>();
var iterator = new Iterator(typedEnumerator);

Now that we have in iterator, we have in effect an IEnumerable, and so we can use some nice extension method, from the Linq API. For instance, just combining Take(), Skip() and the magic yield return instruction, we can easily write extension methods to visit a bi-dimensional array by rows or by columns,

public static IEnumerable<IEnumerable<T>> Rows<T>(this T[,] table)
{
  var rows = table.GetLength(0);
  var cols = table.GetLength(1);

  for (int r = 0; r < rows; r++ )
    yield return new Iterator<T>(
      table.GetEnumerator().AsEnumerator<T>())
      .Skip(r*cols).Take(cols);
}
public static IEnumerable<IEnumerable<T>> Columns<T>(this T[,] table)
{
  var cols = table.GetLength(1);
  for (int c = 0; c < cols; c++)
    yield return new Iterator<T>(
      table.GetEnumerator().AsEnumerator<T>())
      .Where((d, i) => i % cols == c);
}

Just put the previous two static methods inside a static class and you have powerful extension methods you can use to extract stats like these:

_2Darray = new int[100,100];
//Initialize the array here... 

int[] rowTotals = new int[100];
for (int r = 0; r < 100; r++)
{
  rowTotals[r] = _2Darray.Rows().ElementAt(r).Sum();
}

int[] colTotals = new int[100];
for (int c = 0; c < 100; c++)
{
  colTotals[c] = _2Darray.Columns().ElementAt(c).Sum();
}

And what about finding the best column? This is the code:

var bestCol = table.Columns()
  .Select((c,i)=>new {Idx=i, Total = c.Sum()})
  .OrderByDescending(x => x.Total)
  .First();
Console.WriteLine("Best column is {0}, total: {1}",
bestCol.Idx, bestCol.Total);

Your imagination is the limit

You can use the Column() extension method to sort of transposing the GetEnumerator() method that comes with the Array class. The following code produces an array joining the columns of a bi-dimensional array called _2DArray:

var lineArray = _2Darray.Columns().SelectMany(n=>n).ToArray();
1 2 3 4
5 6 7 8
9 10 11 12

Fig1 - _2DArray.

1 5 9 2 6 10 3 7 11 4 8 12

Fig2 - lineArray

Finally, the following extension method lets you invert the process, starting from a 1-dimensional array and "wrapping" it over a bi-dimensional layout.

public static IEnumerable<IEnumerable<T>> HorizontalWrap<T>(
this IEnumerable<T> collection, 
int width)
{
  yield return new Iterator<T>(collection.Take(width));
}

For instance, let say we have the following array of 31 numbers:

1 2 3 4 ... ... 28 29 30 31

then we can use the HorizontalWrap() method to split the array in chunks of 7 cells and use them to fill a jagged-array (does this remind you of something?):

var days = Enumerable.Range(1,31).ToArray();

var weeksCollection = days.HorizontalWrap(7); 
int[][] weeks = new int[7][];
int weekNo = 0;
foreach(var week in weeksCollection)
{
  weeks[weekNo] = week.ToArray();
  weekNo++;
}

Happy programming!

No comments: