miércoles, 9 de diciembre de 2009

mapping through many lists, perl vs lisp

Last night (or this if you prefer), I was reading about lisp mapcar function, and I mentally mapped it to perl's map. (More precisely, I was reading exactly page 224 (or 232) of This book)

In fact, they do quite the same, but mapcar (that is a specialized form of lisp's map) seems a bit more flexible because it can deal with many lists at once (perl's map can't, at least by default).

First we start with a usual applicative programming case:

For example, generating a list with the square of each element in another list.

perl version looks like this:

my @squares = map { $_ * $_ } @mylist;
#or
map { $_ * $_ } (1..10);

lisp version. As lisp does not have the equivalent to (1..10), we can build a list builder by range :
(defun range (a b)
(loop for i from a to b collect i))
(range 1 10) => (1 2 3 4 5 6 7 8 9 10)
(mapcar #'(lambda (x) (* x x)) (range 1 10)) => (1 4 9 16 25 36 49 64 81 100)
That's the full interaction we have to do with lisp. define range function, test it, and then use mapcar. Notice that common lisp (unlike scheme) is a lisp-2, so has a namespace for variables and another for functions, so we have to provide the sharp quote, that refers to the function.

So far so good.

But when things get trickier is when you want to evaluate a function that takes two parameters and you want to get the parameters from two lists.

lisp mapcar can do it by default. (nice, eh?)
(mapcar #'+ '(1 2) '(2 1)) => (3 3)
Doing this in perl is not that straight, but it is possible, and you know, TIMTOWTDI:

A nice trick is doing a little mind change , and shift the point of view. You can take the index as the iterator, and get something like:

map { $a[$_] + $b[$_] } 0..$#a ;
Btw, remember that if you feed 2 or more lists to map, it will append them and treat as if only one list was entered.

There are other ways to get the map functionality, using some List::MoreUtils functions.

For example, if you only want to operate on two lists, you can use pairwise:
pairwise {$a + $b} @a , @b ;
There's also each_array, that builds an iterator , and with it, you can browse N arrays step by step.

As a last hacky resort, you could use zip (or mesh, they are the same) to build a new list like (11,21,31,12,22,32....) (I hope you get what I mean) , and then use natatime.

Want more perl ways to do it? Check that stackoverflow thread.

Plenty of options, yes, I know. For me, I stick with good old map {..} 0...$#a ;

As always, thanks to #emacs-es'ers for patiently answering my Offtopic lisp questions.
And thanks to Larry for the language that makes programming -Ofun.
And remember, comments allowed, even encouraged. :-)

No hay comentarios: