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

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

Hi, Following ( snippet ) code works fine:
foreach my $x (@names) { if ( $x =~ /test$/) { print "$path_name\n"; } }
Instead of above one, i want to keep it simple as follows:

print "$path_name\n" if ( $x =~ /test$/) foreach my $x (@names) # Not working

print "$path_name\n" if ( /test$/) foreach (@names) # Not working.

I appreciate your suggestions to optimize it.

Replies are listed 'Best First'.
Re: Concise foreach expression
by LanX (Saint) on Aug 29, 2014 at 12:12 UTC
    You are trying to combine postfix foreach and postfix if and running into two problems:

    1. they can't be chained
    2. they can't use loop-vars other than $_

    Your best bet for a concise expressions to be chained are map and grep

    print grep { /test/ } @names

    update

    N.B.: you are still limited to $_

    update

    which means you won't have much use of nested loops with map

    Cheers Rolf

    (addicted to the Perl Programming Language and ☆☆☆☆ :)

      which means you won't have much use of nested loops with map

      Could you explain what you mean there? map localizes $_, so nested maps are not a problem. Code like the following (admittedly contrived) snippet works as expected ($_ is not clobbered by the inner map):

      my @list = ( ["Barney", "Rubble"], ["Fred", "Flintstone"], ["Betty", "Rubble"], ["Wilma", "Flintstone"] ); map { say "Outer map before: @$_"; map { 1 } @$_; say "Outer map after : @$_"; } @list;

      But perhaps there's other pitfalls, so I'd be grateful for any enlightenment.

        The problem happens when you need to use the $_ from the outer map in the inner map, as in the following contrived example:
        my %tree = ( child1 => {}, child2 => { grandchild1 => { brood3 => {} }, grandchild2 => { brood1 => {}, brood2 => {} }, }, ); say for 'Grandchildren:', map { my $x = $_; # <--- remove me! "$_: " . (ref $tree{$_} ? join ', ' => map keys %{ $tree{$x}{$_} }, keys %{ $tree{$_} } : q()) } keys %tree;
        لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ
        > Could you explain what you mean there?

        I was talking about chaining maps or fors to simulate nested foreach loops.

        I.o.w. list comprehensions are very hard to implement in Perl without nested blocks.

        e.g. try to implement this Python example for "Pythagorean triples" w/o nesting

        >>> [(x,y,z) for x in range(1,30) for y in range(x,30) for z in range( +y,30) if x**2 + y**2 == z**2] [(3, 4, 5), (5, 12, 13), (6, 8, 10), (7, 24, 25), (8, 15, 17), (9, 12, + 15), (10, 24, 26), (12, 16, 20), (15, 20, 25), (20, 21, 29)]

        Cheers Rolf

        (addicted to the Perl Programming Language and ☆☆☆☆ :)

Re: Concise foreach expression
by AppleFritter (Vicar) on Aug 29, 2014 at 12:13 UTC

    Unfortunately, statement modifiers cannot be chained. From perlsyn (emphasis in original):

    Any simple statement may optionally be followed by a SINGLE modifier, just before the terminating semicolon (or block ending).

    In this specific case, you can use grep or map instead, though.

    say foreach (grep /test$/, @names); map { /test$/ and say } @names;

    Edit: toolic's remark in Re^2: Concise foreach expression also applies to the above two lines: they'll print the matching @names, not multiple copies of $path_name.

      > map { /test$/ and say } @names;

      variation:

      /test$/ and say for @names;

      ( of course always with use feature 'say';)

      Cheers Rolf

      (addicted to the Perl Programming Language and ☆☆☆☆ :)

        TMTOWTDI at its best!
Re: Concise foreach expression
by toolic (Bishop) on Aug 29, 2014 at 12:14 UTC
    Your original foreach loop is probably the clearest, but if you do want fewer characters, you could go for something less readable like:
    print"$path_name\n"for grep/test$/,@names;

    I doubt it runs faster.

      print"$path_name\n"x grep/test$/,@names; say$path_name x grep/test$/,@names;
      /test$/&&print$path_name,$/for@names; /test$/&&say$path_name for@names;

      If he meant $x where he had $path_name,

      print"$_\n"x grep/test$/,@names; say for grep/test$/,@names;
      /test$/&&print$_,$/for@names; /test$/&&say for@names;
      print/.*test$/g,$/ for@names; say/.*test$/g for@names;
Re: Concise foreach expression
by hdb (Monsignor) on Aug 29, 2014 at 12:13 UTC

    In this case you could use grep:

    print "$_\n" for grep {/test$/} @names;

    UPDATE: ...or more cryptically: /test$/ and print "$_\n" for @names;.

      That produces different results from the OP's code. Yours prints out elements of the @names array, but the OP prints out $path_name multiple times.

        Thanks! I did really see what I wanted to see and did not read carefully enough!

Re: Concise foreach expression
by johngg (Canon) on Aug 29, 2014 at 12:24 UTC

    I agree that grep is the way to tackle this but just to show another way with a ternary as the argument to print and a statement modifier.

    $ perl -Mstrict -Mwarnings -E ' my @arr = qw{ sdyudf attest reer fittest }; print m{test} ? qq{$_\n} : () for @arr;' attest fittest $

    I hope this is of interest.

    Cheers,

    JohnGG

      Thanks for all code & explanation.

      used map from "AppleFritter".
Re: Concise foreach expression
by davido (Cardinal) on Aug 29, 2014 at 15:25 UTC

    m/test$/ && print "$path_name\n" for @names;

    Dave

Re: Concise foreach expression
by Anonymous Monk on Aug 29, 2014 at 17:40 UTC
    The original snippet works fine, and therefore should require no attention from you. Rewriting it to use different constructs probably won't "optimize" it at all ... and is likely (as you saw) to make it stop working altogether and/or to become much more fragile. You are, if I may politely say, wasting your time.
Re: Concise foreach expression
by VincentK (Beadle) on Aug 29, 2014 at 17:34 UTC
    Hello. I see this one is pretty much answered, but here is my two cents.

    use strict; use warnings; my @names = ('ABC','123test','DEF','Gibberishafdaf','Moretest','Gibber +ishafdaf'); ($_ =~ /test$/) ? print "[$_] Pattern found\n": print "[$_] Pattern NO +T found\n" , for(@names);