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

My objective is simple. I want to perform a substitution during a grep but I do not want the original array elements changed. I already know that you can do this by adding strange characters around the array like {@foo} or eval {@foo}, but why can't I simply localize the $_ inside of the grep?
C:\>perl -e "@foo=('one','two'); %m=('1'=>'one', '2'=>' two'); @m_h= grep{local($_);s/tw/tr/g; /^t.*/ig} @foo; print @m_h; pri +nt @foo" onetwo
Note: Take out the local($_) call in the above example to see that the substitution will take place but will also actually change the contents of @foo.

Celebrate Intellectual Diversity

Replies are listed 'Best First'.
Re: Localize $_ in grep
by Zaxo (Archbishop) on Sep 09, 2003 at 19:46 UTC

    Your local($_); is producing an undef $_. The match at the end of the grep block is producing a list of the @foo which start with t or T. The $_ grep provides is the external one, not your substituted one.

    You can do what you want with a combination of map and grep my @m_h = map {(my $c = $_) =~ s/$this/$that/ig; $c } grep {/^t/i} @foo; or, maybe, my @m_h = grep {/^t/i} map {(my $c = $_) =~ s/$this/$that/ig; $c } @foo; That will leave @foo undamaged.

    After Compline,
    Zaxo

Re: Localize $_ in grep
by jeffa (Bishop) on Sep 09, 2003 at 19:35 UTC
    Anytime you substitute inside of a grep or map block, you have to do this:
    my @m_h = grep {local($_);s/tw/tr/g; /^t.*/ig;$_} @foo;
    That is, put the variable you are substituted as the last item in the block. Otherwise, you return the return of the substitute, which is usually 1 or the number of matches (i always forget :/).

    In your case, you don't want grep - you want map (actually, you do want grep ... see 2nd update below):

    my @m_h = map {s/tw/tr/g;$_} @foo;
    Notice that you don't need local.

    UPDATE:
    Oops, this still changes the contents of @foo ... hmmm, how about a simple copy?

    my @m_h = @foo; s/tw/tr/g for @m_h;
    Should be all you need. No?

    UPDATE 2:
    After looking at ctilmes reply, how about this:

    my @m_h = grep /^t/i, @foo; s/tw/tr/g for @m_h;

    Hope this helps. :)

    jeffa

    L-LL-L--L-LL-L--L-LL-L--
    -R--R-RR-R--R-RR-R--R-RR
    B--B--B--B--B--B--B--B--
    H---H---H---H---H---H---
    (the triplet paradiddle with high-hat)
    

      Since perl 5.13.2 s/// and tr/// support the /r flag, which returns a copy instead of the number of substitutions. The /r feature is called Non-destructive substitution

      $ perl -MData::Dump -e " @f = 1..4; dd [ map { s/^/F/r; } @f ]; dd\@f; + " ["F1" .. "F4"] [1 .. 4]
Re: Localize $_ in grep (filter)
by tye (Sage) on Sep 09, 2003 at 20:08 UTC

    Perl really needs a filter function. Then I'd use it with grep:

    use filter; @m_h= filter { s/tw/tr/g } grep /^t/i, @foo; # or, if you don't like prototypes: @m_h= filter( sub { s/tw/tr/g }, grep /^t/i, @foo );

                    - tye
Re: Localize $_ in grep
by ctilmes (Vicar) on Sep 09, 2003 at 19:39 UTC
    I think you want map and grep
    @m_h = map { local $_ = $_; s/tw/tr/g; $_ } grep {/^t/i} @foo;
Re: Localize $_ in grep
by hardburn (Abbot) on Sep 09, 2003 at 19:38 UTC

    You could do that, but people generally don't like to clutter grep/map with more statements than are absolutely necessary. A { @foo } is less typing and not so ambigious that I'd feel bad about putting it into production code.

    ----
    I wanted to explore how Perl's closures can be manipulated, and ended up creating an object system by accident.
    -- Schemer

    Note: All code is untested, unless otherwise stated