简体   繁体   中英

In Perl, how can I implement map using grep?

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM