Beefy Boxes and Bandwidth Generously Provided by pair Networks
We don't bite newbies here... much

mapcar -- map for more than one list

by tye (Sage)
on Dec 04, 2000 at 12:21 UTC ( [id://44763] : CUFP . print w/replies, xml ) Need Help??

Update: Now available in several flavors as part of Algorithm::Loops.

runrig was complaining about no mapcar in Perl so I wrote one in the chatterbox but it scrolled off. So here are two better versions.

mapcar is from lisp. It is like map but works on more than one list. While map loops setting $_ to successive elements of a list, mapcar loops setting @_ to successive elements of several lists (but since you can only pass one list to a subroutine, mapcar expects a list of references to one or more arrays). Both map and mapcar collect the values returned by the code block or subroutine and then return the collected values.

# Sample usage: my @array= ( [ 'a'..'c',undef ], [ 1..7 ], [ 'A'..'E' ] ); my @trans= mapcar { [@_] } @array; # @transpose is now ( ['a',1,'A'],['b',2,'B'],['c',3,'C'], # [undef,4,'D'],[5,'E'],[6] ) my @transpose= mapcaru { [@_] } @array; # @trans is now ( ['a',1,'A'],['b',2,'B'],['c',3,'C'], # [undef,4,'D'],[undef,5,'E'],[undef,6,undef] )
So mapcaru puts undefs into @_ when one of the lists is shorter than any of the others while mapcar just leaves values out of @_. Both versions are provided since the output of either cannot be easily converted into the other.

If you find yourself wishing for a special Perl variable that tracks which element of a list you are currently iterating over (in map or for), then you might find that mapcar is useful to you.

#!/usr/bin/perl -w package mapcar; use strict; require Exporter; use vars qw( $VERSION @EXPORT @ISA ); BEGIN { $VERSION= 1.01; @EXPORT= qw( mapcar mapcaru ); @ISA= qw( Exporter ); } sub mapcaru (&@) { my $sub= shift; if( ! @_ ) { require Carp; Carp::croak( "mapcaru: Nothing to map" ); } my $max= 0; for my $av ( @_ ) { if( ! UNIVERSAL::isa( $av, "ARRAY" ) ) { require Carp; Carp::croak( "mapcaru: Not an array reference (", ref($av) ? ref($av) : $av, ")" ); } $max= @$av if $max < @$av; } my @ret; for( my $i= 0; $i < $max; $i++ ) { push @ret, &$sub( map { $_->[$i] } @_ ); } return wantarray ? @ret : \@ret; } sub mapcar (&@) { my $sub= shift; if( ! @_ ) { require Carp; Carp::croak( "mapcar: Nothing to map" ); } my $max= 0; for my $av ( @_ ) { if( ! UNIVERSAL::isa( $av, "ARRAY" ) ) { require Carp; Carp::croak( "mapcar: Not an array reference (", ref($av) ? ref($av) : $av, ")" ); } $max= @$av if $max < @$av; } my @ret; for( my $i= 0; $i < $max; $i++ ) { push @ret, &$sub( map { $i < @$_ ? $_->[$i] : () } @_ ); } return wantarray ? @ret : \@ret; } 1;

Replies are listed 'Best First'.
(tye)Re1: mapcar -- map for more than one list
by tye (Sage) on Dec 04, 2000 at 12:32 UTC

    I guess it isn't my night for posting snippets. (:

    I can now edit snippets without problems so I moved the code up where it should be.

            - tye (but my friends call me "Tye")
      'Complaining' is a rather strong word... :-) I'd say it was just another "gee, it'd be nice" things. ++ for ya anyway, thanx!

        Sorry. I sometimes also get in trouble for saying "steal" when I mean "borrow". (:

        "Were'd you get that, Tye?"

        "Oh, runrig stole it for me."

                - tye (but my friends call me "Tye")
Re: mapcar -- map for more than one list
by mugwumpjism (Hermit) on Dec 01, 2001 at 00:50 UTC

    I'm not sure this is right.

    The e-LISP mapcar, according to the info page "(elisp) Mapping Functions", is defined as follows:

     - Function: mapcar FUNCTION SEQUENCE
         `mapcar' applies FUNCTION to each element of SEQUENCE in turn, and
         returns a list of the results.
         The argument SEQUENCE can be any kind of sequence except a
         char-table; that is, a list, a vector, a bool-vector, or a string.
         The result is always a list.  The length of the result is the
         same as the length of SEQUENCE.

    IANALH, but that sounds remarkably like Perl's map function.

    However, it does go on to provide an example which seems to do (almost) the same thing that your mapcar does:

    (defun mapcar* (function &rest args) "Apply FUNCTION to successive cars of all ARGS. Return the list of results." ;; If no list is exhausted, (if (not (memq 'nil args)) ;; apply function to CARs. (cons (apply function (mapcar 'car args)) (apply 'mapcar* function ;; Recurse for rest of elements. (mapcar 'cdr args))))) (mapcar* 'cons '(a b c) '(1 2 3 4)) => ((a . 1) (b . 2) (c . 3))

    Besides that, "car" is an old term coming from the term "Contents of the Address part of the Register". I don't know where you got mapcaru from.

    Perhaps this is a common lisp vs e-lisp thing, as I found at least one page that makes reference to mapcar behaving in the way you specify.

    I suggest the name mapshift instead of mapcar, for the version that returns short lists. The other one should perhaps be called something else, like mapfor or mapforeach perhaps.

    Here is a new one, too - mapeach, which works on hash refs passed to it:

    sub mapeach (&\%) { my $sub = shift; my $hash = shift or do { require Carp; Carp::croak( "mapeach: Nothing to map" ); }; my @ret; while ( my ($k, $v) = each %{$hash}) { local ($_) = $k; push @ret, $sub->($k, $v); } return wantarray ? @ret : { @ret }; }
      Some lisp's have a limited mapcar which can only operate on one list (making it like perl's map), but in other lisp's mapcar will operate on a list of lists, taking the car of each list, making a list from that, and using that as the first argument to the function, then taking the next element of each list, etc., and returning a list of the results. I think Common Lisp has the limited version, I'm not positive. The Lisp's I've used, AutoLISP and UCI LISP (long ago), both would take a list of lists.

      And tye's 'mapcaru' ('mapcar', but use the (u)ndefined value for missing elements) was just a different implementation which deals with lists of differing lengths in a different way.

        Common Lisp's mapcar can accept lists of lists. However it only iterates as long as all lists have more elements. In other words its handling of unequal lists is to stop rather than trying to continue calling while indicating the missing elements in some way.
Re: mapcar -- map for more than one list
by dragonchild (Archbishop) on Jan 21, 2002 at 22:23 UTC
    Some coding style questions:
    1. Why do you use if (! ...) and not unless (...)?
    2. Why do you use require and not use?
    3. Why didn't you extract out the common parts of mapcar() and mapcaru() and call some other function? Was it speed issues? (This would indicate we need a #define in Perl...)

    We are the carpenters and bricklayers of the Information Age.

    Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

      1. I don't see unless as being any clearer. I rarely use unless. I consider its real value to be providing a way to not have to say if( ! ( complex combination of Boolean expressions ) ) and other methods often serve that same purpose. I could write:
        if( ! @_ ) if( 0 == @_ ) if( @_ < 1 )
        to list a few. I read the first one as "if no arguments", which is perfectly clear to me, clearer than "unless some arguments". But it all seems rather trivial in this case and any of them (including using unless) would be fine by me. (: (I guess I'm more likely to use unless as a statement modifier, not much for a conditional block.)
      2. use would cause be loaded at compile-time. I don't want to load unless I actually end up needing it. I wouldn't go to this much work in a script, but I pay attention to minor details when writing modules.
      3. Mostly because the code started out simpler and the error handling was expanded later and the code hasn't been refactored since then. :)

      Update: dragonchild tells me that the question was more about require Exporter. use would call Exporter->import and I'm not importing anything from Exporter so I see no point in useing it. Even Exporter docs use require and not use.

              - tye (but my friends call me "Tye")
        A note about if not versus unless.

        I am diametrically opposed to tye here. The only win for unless is to make some things be said in a way that more directly matches how we speak. But I would *never* use it for complex expressions. As I have found from painful experience, people do not apply De Morgan's laws on the fly. In other words while debugging it takes a lot of thought to translate:

        unless (A or B) { .... }
        and recognize that as
        if (!A and !B) { ... }
        After you have been there a couple of times, you learn not to use unless with complex expressions. :-)
Re: mapcar -- map for more than one list
by mugwumpjism (Hermit) on Jan 07, 2004 at 04:49 UTC

    This is now on CPAN as Maptastic

    $h=$ENV{HOME};my@q=split/\n\n/,`cat $h/.quotes`;$s="$h/." ."signature";$t=`cat $s`;print$t,"\n",$q[rand($#q)],"\n";

      Let me guess: You didn't follow the standard protocol of bringing this module up on the modules list for naming / namespace approval before polluting CPAN with it, did you?