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

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

Is it possible to use a variable in place of the expression or block of grep? Am I missing something simple or is this simply illegal?

With the simple fragment below, I would like to get a result of 1,2 for both greps but I get all elements on the second. Btw, I get the same results from the expression version: "grep $var, @arr".

use strict; use warnings; my @arr = (1, 2, 3, 4, 5); my $pat = '$_ < 3'; my @res; @res = grep {$_ < 3} @arr; print "Literal:\t@res\n"; @res = grep {$pat} @arr; print "Variable:\t@res\n";

Of course, this is just a test script. The real thing builds the grep pattern in several steps and searches a two-dimensional array. I could do all this differently but the most obvious alternative would be a long list of if statements.

Replies are listed 'Best First'.
Re: grep { $var } @arr
by Athanasius (Archbishop) on Jul 17, 2014 at 14:56 UTC

    Another option is to use a subroutine reference instead of a string:

    #! perl use strict; use warnings; my @arr = (1, 2, 3, 4, 5); my $pat = sub { $_[0] < 3 }; my @res; @res = grep {$_ < 3} @arr; print "Literal:\t@res\n"; @res = grep { $pat->($_) } @arr; print "Variable:\t@res\n";

    Output:

    0:52 >perl 939_SoPW.pl Literal: 1 2 Variable: 1 2 0:52 >

    Hope that helps,

    Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

Re: grep { $var } @arr
by toolic (Bishop) on Jul 17, 2014 at 14:53 UTC
    eval seems to work (with the usual security disclaimers):
    @res = grep {eval $pat} @arr;
      eval $pat

      Thanks! I feel like a total fool but it's worth having such a simple fix.

      This works on my overly complex, multi-dimensional problem as well.

Re: grep { $var } @arr
by LanX (Saint) on Jul 17, 2014 at 14:58 UTC
    > Am I missing something simple or is this simply illegal?

    Its code, so put it into a function. grep allows replacing the EXPR with a function call and $_ is global anyway. :)

    Cheers Rolf

    (addicted to the Perl Programming Language)

Re: grep { $var } @arr
by Anonymous Monk on Jul 17, 2014 at 14:58 UTC

    You are not using a pattern which, to me, implies m// or index() use; you are actually using a code block.

    Use a list of sub references to iterate over instead ...

    @test = ( sub { $_[0] < 3 } ); for my $t ( @test ) { print join q[ ] , grep $t->( $_ ), 1 .. 5; }
Re: grep { $var } @arr
by bulrush (Scribe) on Jul 17, 2014 at 16:53 UTC
    Yes you can do that but I usually do it like this:
    @res = grep (/$pat/g, @arr);
    
    Perl 5.8.8 on Redhat Linux RHEL 5.5.56 (64-bit)
      Well, bulrush, you did not say how you populate $pat, but I fail to see how a regex would figure out whether a value is larger or smaller than 3.

      I definitely agree that a subroutine reference is a very good solution, perhaps the best.

        I fail to see how a regex would figure out whether a value is larger or smaller than 3.

        You can get clever with match-time pattern interpolation. For instance:

        my @list = (1..5); my $pattern = qr/\d+(??{if($& < 3) { "" } else { "(?<!\\d)" } })/; my @res = grep /$pattern/, @list; say join ", ", @res;

        This also (ab)uses negative look-behind assertions to create a subpattern that is guaranteed to fail (in this regex, not in general).

        But I agree, subroutine references are the way to go here.

      /$pat/g

      Perhaps I've misunderstood but with my current $var, using /$pat/g will result in no matches (with or without the 'g' modifier). As far as I know, this will look for the literal string of '$_ < 3' on my array, as written below (appending to the initial script).

      @res = grep {/$pat/g} @arr; print "Search:\t@res\n";

      This finds none of the elements of @arr.