Posted on 11/19/2007 10:29:01 AM by Justin Etheredge
I figured since we went over the "Fold" (also called Reduce) higher order function in the last post, we would go over the "Map" higher order function in this post. Since everyone these days has heard of Map-Reduce because of Google, I figured I would try and close the circle. Map is actually more simple than reduce, it just takes a set of items and a method. It then runs this method on each item in the set. We are going to just create this function as an extension method, since it will make it much easier to use. Our "Map" function would end up looking like this:
public static IEnumerable<TResult> Map<TArg, TResult>(
this IEnumerable<TArg> list,
Func<TArg, TResult> func)
{
if (list == null || list.Count() == 0)
{
return new List<TResult>();
}
else
{
List<TResult> result = new List<TResult>();
result.Add(func(list.First()));
result.AddRange(
Map(list.Skip(1).Take(list.Count() - 1).ToList(), func));
return result;
}
}
This function may look a little bit daunting at first, but trust me, it is actually really simple. The first parameter "this IEnumerable<TArg> list" is our list of data that we are going to operate on. We have added "this" on the front of it because this is an extension method that is called like "IEnumerable.Map();". You will have parameters, but we will get to that in just a second. Our second parameter "Func<TArg, TResult> func" is the method that we are going to apply against the items in our list.
The first part of the method is just the base case for our recursion. If this list is empty or null then we just return an empty list. The second part of our method declares a new List as a result and then adds the first item in the list to it after applying the specified method. Then we call Map recursively passing in the tail of the list (everything except the first item). Here we are using the Linq extension methods to do this, but you could do it however you wanted to. Then we simply return the list that we created. Easy as pie! You would call this method like this:
var result = numbers.Map(x => x * 2);
This would return to you a list of the same size with every item in the list multiplied by 2. And there you have it! We can combine our map and reduce methods like this. This will multiply all the items in the list and then add the results up into an integer.
var comboResult =
numbers.Map(x => x * 2).Fold((int x, int y) => x + y);
Pretty awesome, huh? So, now we have learned how to "map" a function to all the items in a list and then "fold" that list back into a single result. In the future I might have a post that will get into the usefulness of these a bit more, and show you how we could extend these to create more useful functions. In the meantime, get out there and come up with a creative use for it! Be sure to let me know how you use it.
Update:
Someone posted a comment below about how inefficient the Map function I had was. I did it the way that I did because I was trying to implement it in a more functional style. But since I implemented some of my other methods in a more imperative manner I figured I would add an updated version that is more efficient.
public static IEnumerable<TResult> Map<TArg, TResult>(
this IEnumerable<TArg> list,
Func<TArg, TResult> func)
{
List<TResult> result = new List<TResult>();
foreach (TArg item in list)
{
result.Add(func(item));
}
return result;
}