http://www.perlmonks.org?node_id=621475

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

I have an array of strings. I want to wrap each string in quotation marks—that is, to prepend and append a quotation mark to each string—unless the string matches a certain pattern. There are many ways to do this, but I want to know why I'm having trouble doing it with map. Here is some example code:

my @strings = qw(boy bird FALSE); @strings = map { unless (/FALSE/) { "\"$_\"" }} @strings;

I want this to return

"boy" "bird" FALSE

but it returns

"boy" "bird" 1

and I can't quite figure out why. The 1 appears to come from scalar evaluation of unless (/FALSE/), but why? It seems that $_ is being set to 1 at some point, but I don't see why that should be, either.

Replies are listed 'Best First'.
Re: using map with compound code blocks to alter strings: a strange result
by kyle (Abbot) on Jun 15, 2007 at 15:53 UTC

    In the case of "FALSE", the block is returning the value of the last expression evaluated. That was /FALSE/, which is true (i.e., 1).

    What you might want to do is something like this:

    @strings = map { /FALSE/ ? $_ : qq{"$_"} } @strings;
Re: using map with compound code blocks to alter strings: a strange result
by eric256 (Parson) on Jun 15, 2007 at 19:03 UTC

    Map puts the return from the code block in the array, not a modified version of $_. This is a common gotcha especialy if you want to run a regex on $_ and return the result.

    use Data::Dumper; my @test = qw/hello world/; print Dumper( map { s/o// } @test ), "\n"; print Dumper( map { s/o//; $_ } @test ), "\n";

    Outputs:

    $VAR1 = 1; $VAR2 = 1; $VAR1 = 'hell'; $VAR2 = 'wrld';

    Notice how the first returns that same rouge 1 like you are getting. So you just have to make sure that the last statement in your map code block is returning the value you want to insert into the array.


    ___________
    Eric Hodges
Re: using map with compound code blocks to alter strings: a strange result
by hangon (Deacon) on Jun 16, 2007 at 06:37 UTC

    Being somewhat map challenged, I didn't really grasp what's happening until I tried it this way. Maybe this will help to clarify for someone:

    my @strings = qw(boy FALSE bird); @strings = map { unless (/FALSE/) { "\"$_\"" }else{$_}} @strings;
Re: using map with compound code blocks to alter strings: a strange result
by andreas1234567 (Vicar) on Jun 15, 2007 at 17:56 UTC
    I would narrow the selection down first with grep, then transform:
    $ perl -l use strict; use warnings; my @strings = qw(boy bird FALSE); @strings = map { qq{"$_"} } grep( !/FALSE/, @strings); print join (q{,}, @strings); __END__ "boy","bird" $
    Update:As kyle points out, this solution is both wrong and inefficient.
    --
    print map{chr}unpack(q{A3}x24,q{074117115116032097110111116104101114032080101114108032104097099107101114})

      The problem with this is that the OP said that the desired result is a list that includes every element in the original list. Some will be transformed and some won't.

      Another problem is more subtle. When you grep first and then map, you loop over the list twice. If it's a very long list, this will often be a waste of time. The only time I'd want to do it is if I expect grep is going to remove a lot of the list and/or the map's work is especially time consuming. In most other cases, it's better to make the decisions in the map only.

      Even when you want to remove elements, you can have the map block return () for the elements to remove. The code you wrote could look like this:

      my @strings = qw(boy bird FALSE); @strings = map { /FALSE/ ? qq{"$_"} : () } @strings; print join q{,}, @strings;

      It does the same thing, but it's only one loop.