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;
(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")
| [reply] |
|
'Complaining' is a rather strong word... :-) I'd say it was just another "gee, it'd be nice" things. ++ for ya anyway, thanx!
| [reply] |
|
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")
| [reply] |
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 };
}
| [reply] [d/l] [select] |
|
| [reply] |
|
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.
| [reply] |
Re: mapcar -- map for more than one list
by dragonchild (Archbishop) on Jan 21, 2002 at 22:23 UTC
|
Some coding style questions:
- Why do you use if (! ...) and not unless (...)?
- Why do you use require and not use?
- 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. | [reply] [d/l] [select] |
|
| [reply] [d/l] [select] |
|
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. :-) | [reply] [d/l] [select] |
|
|
Re: mapcar -- map for more than one list
by mugwumpjism (Hermit) on Jan 07, 2004 at 04:49 UTC
|
| [reply] [d/l] |
|
| [reply] |
|
|