LameNerd has asked for the wisdom of the Perl Monks concerning the following question:

I have this for loop that I think I should change to a map call
for my $value ( @my_array ) { $value = s/^CHOP_ME_OFF_//g; push ( @my_array_2, $value ); }
I tried unsuccessfully doing this with map.
@my_array_2 = map( @my_array, s/^CHOP_ME_OFF_//g );
What should I have done instead?

Replies are listed 'Best First'.
Re: how could I use map here?
by Cody Pendant (Prior) on Feb 06, 2003 at 21:37 UTC
    Call me Mister Obvious, but this works too:
    @array2 = @array1; s/^CHOP_ME_OFF_// for @array2;

    --
    “Every bit of code is either naturally related to the problem at hand, or else it's an accidental side effect of the fact that you happened to solve the problem using a digital computer.”
    M-J D

      This is for those that like to keep it one line, and map()ers usually do. :)   s/^CHOP_ME_OFF_// for @array2 = @array1; (Not that it's likely that I'd do that myself -- I prefer to have my assignment a bit more towards BOL...)

      ihb
Re: how could I use map here?
by dws (Chancellor) on Feb 06, 2003 at 20:42 UTC
    I tried unsuccessfully doing this with map. ...

    You're running into two pitfalls. A slight rewrite might make it clearer. Consider:

    @my_array_2 = map { s/^CHOP_ME_OFF_//g } @my_array;
    But this won't work. It gives you an array full of integers, and it modifies the contents of @my_array.

    In this case, it's better to use substr.

    @my_array_2 = map { /^CHOP_ME_OFF_/ ? substr($_, length("CHOP_ME_OFF_")) : $_ } @my_array;

    Update: On reflection, might be better off localizing $_

    @my_array_2 = map { local $_ = $_; s/^CHOP_ME_OFF_//; $_ } @my_array;
      Re your last solution: what do you gain by localizing $_? Or, equivalently, what is the difference between

      @my_array_2 = map { local $_ = $_; s/^CHOP_ME_OFF_//; $_ } @my_array;
      and

      @my_array_2 = map {s/^CHOP_ME_OFF_//; $_ } @my_array;
      I've used the latter version often and it seemed to work perfectly fine...

      pike

      Update:OK, to be honest what I really used was

      @my_array = map {s/^CHOP_ME_OFF_//; $_ } @my_array;
      i. e. I did the replacements in-place, which explains why I never noticed the effect pointed out by adrianh++.

        Without the local the original array is changed. Since $_ is aliased to each element the s// changes the original array. Try:

        my @x = qw(foo bar CHOP_ME_OFF_foo); my @y = @x; my @copies = map { local $_ = $_; s/^CHOP_ME_OFF_//; $_ } @x; my @changes = map {s/^CHOP_ME_OFF_//; $_ } @y; print "x @x\ny @y\ncopies @copies\nchanges @changes\n";

        adrianh++

        It's often (always?) a good practice to localize $_ whenever you need to use it, like any other global variable: it could be accessed by your program, or used by modules (File::Find is one of them AFAIK) and it's not fair to mess up with it.

        With localizing it you have a better protection: if something elsewhere in your code relied on $_'s value you won't be messing with it.

        Ciao!
        --bronto


        The very nature of Perl to be like natural language--inconsistant and full of dwim and special cases--makes it impossible to know it all without simply memorizing the documentation (which is not complete or totally correct anyway).
        --John M. Dlugosz
Re: how could I use map here?
by thelenm (Vicar) on Feb 06, 2003 at 20:43 UTC
    Are you trying to build up a new array of strings with "CHOP_ME_OFF_" removed? First of all, I'm guessing you want to change $value = s/^CHOP_ME_OFF_//g to $value =~ s/^CHOP_ME_OFF_// (using =~ instead of =, and no /g necessary, since it can only match once).

    Note: if you do that, then the actual values in @my_array will be modified, since $value is an alias for each one. In that case, you don't need @my_array_2 at all because @my_array will contain the modified values. You might write the code like this:

    s/^CHOP_ME_OFF_// for @my_array;
    If you don't want to modify the original array, then localize $_ or use a temporary variable in your map, maybe like this:
    @my_array_2 = map {local $_=$_; s/^CHOP_ME_OFF_//; $_} @my_array;

    -- Mike

    --
    just,my${.02}

Re: how could I use map here?
by grantm (Parson) on Feb 06, 2003 at 20:50 UTC

    You're almost there, map expects a code block followed by a list eg:

    @my_array_2 = map { s/^CHOP_ME_OFF_//g } @my_array;

    map will create a new list then execute the code block for each element in the original list and add the return value of the block to the new list.

    The trap to watch out for is that s/// returns a count of the number of substitutions performed (or undef if nothing matched). It does not return the contents of the string after the substitution. So the real answer is:

    @my_array_2 = map { s/^CHOP_ME_OFF_//g; $_ } @my_array;

    Update: Oops, I forgot about the other trap (modifiying the original array) but you already go that from the other answers.

Re: how could I use map here?
by BrowserUk (Patriarch) on Feb 06, 2003 at 21:11 UTC

    Slight variation.

    @array2 = map{ /^(?:chop_me_off)?(.*)$/; $1 } @array;

    Examine what is said, not who speaks.

    The 7th Rule of perl club is -- pearl clubs are easily damaged. Use a diamond club instead.

      or even simpler: @array2 = map /^(?:chop_me_off)?(.*)/, @array2; m// in list context returns a list of $1, $2, ...

      -- Hofmator

      I stay away from using the above technique using .* as a "get everything" pattern. That is mostly because the /s is almost always forgotten. I tend to forget it less nowadays, but even though I manically think about it when I read other's code I sometimes miss it in my own code.

      In your code above it leads to an undef entry for every value that has a newline somewhere after "chop_me_off" and before the last char in the string. And then again we have the max limit for quantifiers. See perl -Mre=debug -wle '"a" =~ /a*/'.

      ihb
Re: how could I use map here?
by J9 (Beadle) on Feb 06, 2003 at 20:40 UTC
    Forgive me if I am totally wrong but I think you need to swap the action you want to perform wround with your array. Something like this.

    @my_array_2 = map( s/^CHOP_ME_OFF_//g , @my_array );Very untested.