Some time ago I was asked the “strange” question how would I implement map
with grep
. Today I tried to do it, and here is what came out. Did I squeeze everything from Perl, or there are other more clever hacks?
#!/usr/bin/env perl
use strict;
use warnings;
use 5.010;
sub my_map(&@) {
grep { $_= $_[0]->($_) } @_[1..$#_];
}
my @arr = (1,2,3,4);
#list context
say (my_map sub {$_+1}, @arr);
#scalar context
say "".my_map {$_+1} @arr;
say "the array from outside: @arr";
say "builtin map:", (map {$_+1} @arr);
Are you sure they didn't ask how to implement grep
with map
? That's actually useful sometimes.
grep { STMTs; EXPR } LIST
can be written as
map { STMTs; EXPR ? $_ : () } LIST
(With one difference: grep
returns lvalues, and map
doesn't.)
Knowing this, one can compact
map { $_ => 1 } grep { defined } @list
to
map { defined ? $_ => 1 : () } @list
(I prefer the "uncompressed" version, but the "compressed" version is probably a little faster.)
As for implementing map
using grep
, well, you can take advantage of grep
's looping and aliasing properties.
map { STMTs; EXPR } LIST
can be written as
my @rv;
grep { STMTs; push @rv, EXPR } LIST;
@rv
My attempt at this largely pointless academic exercise is.
sub my_map (&@) {
my $code = shift;
my @return_list;
grep {
push @return_list, $code->($_);
} @_;
return @return_list;
}
Using grep
for this is a bit of a waste, because the return list of map may not be 1:1 with the input list, like my %hash = map { $_ => 1 } @array;
, you need to be more generic that using the return list of grep. The result is any looking method that allows modifying the original list would work.
sub my_map (&@) {
my $code = shift;
my @return_list;
push @return_list, $code->($_) for @_;
return @return_list;
}
I'm not entirely sure what you mean (what aspect of map
ist to be emulated by grep
), but a typical map-scenario could be eg y = x**3
:
...
my @list1 = (1,2,3,4,5);
my @list2 = map $_**3, @list1;
...
With grep
, if you had to, you could almost make it looke like map
(but destroying the original list):
...
my @list2 = grep { $_**=3; 1 } @list1;
...
by simply writing to the references of the original list elements. The problem here is the unwanted modification of the original list; this is what you don't want to do with map
.
Therefore, we could just generate another list in a subroutine, modify this one and leave the original list untouched. In slight modification of Sinan Ünür's solution , this would read:
sub gap(&@) {
my $code = shift;
my @list = @_;
grep $_ = $code->($_), @list;
@list
}
my @arr = (1 .. 5);
# original map
print join ',', map { $_**3 } @arr;
# 1,8,27,64,125
# grep map
print join ',', gap { $_**3 } @arr;
# 1,8,27,64,125
# test original array
print join ',', @arr;
# 1,2,3,4,5 => untouched
Regards
rbo
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.