Wednesday, June 27, 2007

Today, I want to take a brief detour from our excursions into C# 2.0 and examine the C# 3.0 example from my recent article on higher-order functions:

static void Main()
{
  int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };

  int sum = numbers.Filter(x => (x % 2) == 0).Map(x => x * x).Reduce(0, (x, y) => x + y);

  Console
.WriteLine("Sum: {0}", sum);
}

The calculation being performed (summing the squares of the even numbers in an array) is not complicated, but I want to clarify that we don't actually have to define Filter, Map and Reduce in C# 3.0. While those names might be familiar to functional programmers, they already have equivalents in the .NET Framework 3.5.

  1. Filter = Where
  2. Map = Select
  3. Reduce = Aggregate

Each of these are implemented as extension methods for IEnumerable<T>. So, we can rewrite the code like this:

static void Main()
{
  var numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };

  var sum = numbers.Where(x => (x % 2) == 0).Select(x => x * x).Aggregate(0, (x, y) => x + y);

  Console
.WriteLine("Sum: {0}", sum);
}

The Aggregate method is a powerful way to create custom accumulations from an IEnumerable<T>. However, the .NET Framework 3.5 also provides several methods for common accumulations (e.g. Sum, Average, Count, Min, Max). In this situation, we could just use the Sum method.

static void Main()
{
  var numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };

  var sum = numbers.Where(x => (x % 2) == 0).Select(x => x * x).Sum();

  Console
.WriteLine("Sum: {0}", sum);
}

Finally, C# 3.0 provides a natural way to express the Where and Select calls using a query expression.

static void Main()
{
  var numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };

  var sum = (from n in numbers
             where (n % 2) == 0
             select n * n).Sum();

  Console
.WriteLine("Sum: {0}", sum);
}

While defining Filter, Map and Reduce are useful in C# 2.0, they are redundant in C# 3.0. We can use features already present instead of reinventing the wheel.

Have fun!

posted on Wednesday, June 27, 2007 2:54:58 PM (Eastern Standard Time, UTC-05:00)  #    Comments [6]

kick it on DotNetKicks.com
Wednesday, June 27, 2007 3:44:50 PM (Eastern Standard Time, UTC-05:00)
I have a similar approach for C# 2.0.
http://thought-tracker.blogspot.com/2007/04/higher-level-abstractions-on.html
The aggregation/reduce is expressed as InjectInto (a smalltalk influence)

regards,
andrei
Wednesday, June 27, 2007 5:01:58 PM (Eastern Standard Time, UTC-05:00)
I like the solution in IronPython better...

sum([x*x for x in range(1,16) if x%2==0])
Eddie
Thursday, June 28, 2007 9:08:51 AM (Eastern Standard Time, UTC-05:00)
Agreed. Python list comprehensions make this sort of simple calculation pretty trivial. :-)

The Ruby version also isn't bad:

(1..15).select { |x| x % 2 == 0 }.map { |x| x ** 2 }.inject { |x,y| x + y }

Haskell might look like this:

sum (map (^2) (filter (\x -> x `mod` 2 == 0) [1..15]))
Friday, July 13, 2007 3:37:36 PM (Eastern Standard Time, UTC-05:00)
Great series of articles. Thanks for writing it. In general, thanks for writing your blog. I really enjoy it.

Shouldn't "While defining Filter, Map and Reduce are useful in C# 2.0, they are redundant in C# 3.0" read say, "3.0", not "3.5"?

Thanks,

Leif
Sunday, July 15, 2007 5:53:33 AM (Eastern Standard Time, UTC-05:00)
I must be missing something. Where do I refer to C# 3.5?

The language C# 3.0 is build upon the .NET Framework 3.5.
Tuesday, July 31, 2007 9:08:00 PM (Eastern Standard Time, UTC-05:00)
I really have enjoyed this blog series. It's been inspirational to me. I was trying to work up a brief post about my first adventures in C# 2.0 functional programming but when I went to link to your blog I realized had no idea what your name was until I started browsing comments. I suppose it's possible that I'm the last person to discover this blog and that you need no introduction. :)

-Sean-
Comments are closed.