September 10, 2024

Map and filter

Map and filter (along with "Fold") are the heart of what is called functional programming. So, yes, I am going to drag you into a bit of functional programming.

C# does not have map and filter by that name. It does have the same functionality (albeit in not quite so pretty a way) as part of LINQ. In LINQ we use "Select" and "Where" to accomplish the same.

We will postpone LINQ for now and talk about map and filter. In the world of functional programming, we rarely use loops. We don't even use nice "foreach" loops. What we do is process lists and transform one list into another. Processing a list one by one is pretty much a loop, eh? So here are examples of map and filter in Haskell:

newlist = map (\x -> x*x) [7,2,3,4,5,6]
even_nums = filter (\n -> n `mod` 2 == 0) [1,2,3,4,5,6,7,8,9]
The first example replaces each value in the list with the square of its value.
The second example extracts all the even numbers from the list.

The general form of these is "new = map func list" and "new = filter func list".
In the case of map, the function takes one element and return a new one.
In the case of filter, the function takes one element and returns true or false.

I slipped in something extra though. Both of these have a lambda expression for the function. My intent is to make this a sneaky way of talking about lambda functions. Lambda functions are nameless (anonymous) functions intended for one time use at the place where they are defined. C# has a somewhat different syntax. I won't explain the Haskell syntax, but I'll let you puzzle it out given the statement that the following is exactly the same as the first example above:

square x = x * x
newlist = map square [7,2,3,4,5,6]

C# and LINQ

The game now is replicate the above functional Haskell code in C# -- and to use lambda functions while we are at it! If you gave up on or got frightened by the Haskell code above, go back and look at it after absorbing the following code:
using System;
using System.Linq;

using Example;

namespace Example
{
    public class Functional
    {
        public static void IEshow ( IEnumerable<int> v )
        {
            Console.Write ( "Values:" );
            foreach ( int x in v ) {
                Console.Write ( $" {x}" );
            }
            Console.WriteLine ( "" );
        }

        public static void mapper ()
        {
            int [] values = [7,2,3,4,5,6];
            var mapped = values.Select ( x => x*x );
            IEshow ( mapped );
        }

        public static void filt ()
        {
            int [] values = [1,2,3,4,5,6,7,8,9];
            var mapped = values.Where ( x => (x % 2) == 0 );
            IEshow ( mapped );
        }
    }
}

namespace HogHeaven
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Functional.mapper ();
            Functional.filt ();
        }
    }
}
// THE END
There is a lot here. First off note that I slapped in "using Example" so I can avoid typing Example.Functional.mapper () in the HogHeaven namespace.

I added a handy utility method "show()" that prints out a list of integers, as they get returned to me by LINQ. The output from this program is (unsurprisingly):

Values: 49 4 9 16 25 36
Values: 2 4 6 8
Now for the meat of the issue. First look at my "mapper" method. Wierd as it seems, you use "Select" to do a "map" in C#. The name makes little sense, but Select runs over all the values in "values", applies the given function to them, and gathers them up in a new list.

The "filt" method makes more sense, here we use "Where". If you have done any SQL, you use "where" to specify conditions to select records from a database, and that is exactly what we do here.

Now note the syntax for the lambda expressions. We could indeed use C# functions here, adding several lines of code and no real utility. The form is:

argument(s) => body of function

In other words, this lambda function:

x => (x % 2) == 0

is shorthand for:

bool is_even ( x ) {
	return (x % 2) == 0;
}
Now I am wondering if I can write my own map and filter that accept and use lambda functions. Check out the next section for that.


Feedback? Questions? Drop me a line!

Tom's Computer Info / tom@mmto.org