Friday, February 6, 2009

Enumerable character ranges

I was reading Eric Lippert's Blog entry A nasality talisman for the sultana analyst where he was writing some code to help in playing scrabble like games. And I saw a piece of code that I had written myself before and I thought, there must be a better way....

The code:
    foreach (char c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
    {
        // do stuff
    }
what did I not like about the code? the literal ABCDEFGHIJKLMNOPQRSTUVWXYZ, it's too easy to type this in wrong - so I thought I could improve this using the Range function of Enumerable which allows you to write:
    foreach (int i in Enumerable.Range(1, 10))
    {
        // do stuff
    }
neat! but wait a second, the Range function only accepts int as the data type - hmm, so I came up with:
    foreach (int i in
        Enumerable.Range('a', 'z' - 'a' + 1).Select(charCode => (char)charCode))
    {
        // do stuff
    }
way to go! apart from the code being even longer than it was before, and not exactly the sort of code the drips of your finger-tips.

I then found Using LinqPad to Create a Time-Selector Drop-Down List, and the implementation of a "To" extension method:
public static IEnumerable To(this int first, int last) {
  return Enumerable.Range(first, last - first + 1);
}

(Note: on Richard Bushnell's blog I found www.extensionmethod.net a repository of .Net Extension Methods).

I took Richard's implementation and combined it with mine to create a "To" method for characters:
  public static IEnumerable<char> To(this char first, char last)
  {
   var result = Enumerable.Range(first, last - first + 1).Select(charCode => (char)charCode);

   return result;
  }

Which allows you to write:
    foreach (char c in 'A'.To('Z'))
    {
        // do stuff
    }
 // vs.
    foreach (char c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
    {
        // do stuff
    }
And finally, I changed the "To" method to cope with ranges in reverse order:
 public static class EnumerableExtensions
 {
  private static void Swap(ref char a, ref char b)
  {
   var temp = a;
   a = b;
   b = temp;
   // swap code updated to reflect eric's comments
   // http://blogs.msdn.com/ericlippert/archive/2009/02/04/a-nasality-talisman-for-the-sultana-analyst.aspx#comments
  }

  public static IEnumerable<char> To(this char first, char last)
  {
   bool reverseRequired = (first > last);

   if (reverseRequired)
    Swap(ref first, ref last);

   var result = Enumerable.Range(first, last - first + 1).Select(charCode => (char)charCode);

   if (reverseRequired)
    result = result.Reverse();

   return result;
  }
 }
Which allows you to write:
    foreach (char c in 'a'.To('z'))
        Console.WriteLine(c);
    foreach (char c in 'A'.To('Z'))
        Console.WriteLine(c);
    foreach (char c in 'z'.To('a'))
        Console.WriteLine(c);
    foreach (char c in '0'.To('9'))
        Console.WriteLine(c);

No comments: