Beefy Boxes and Bandwidth Generously Provided by pair Networks Cowboy Neal with Hat
No such thing as a small change
 
PerlMonks  

Ways to control a map() operation

by Booger (Pilgrim)
on Jul 15, 2006 at 19:44 UTC ( #561475=perlquestion: print w/ replies, xml ) Need Help??
Booger has asked for the wisdom of the Perl Monks concerning the following question:

Dear Monks:

I came across this statement in the Perl documentation

last cannot be used to exit a block which returns a value such as eval {} , sub {} or do {} , and should not be used to exit a grep() or map() operation.

Does anyone have any explanation as to why I cannot/should not use last to exit a grep() or map() operation?

More to the point, how to I go about exiting a map or grep operation?

Any suggestions or insights into why this is would be greatly appreciated! My greater understanding of map() isn't all that good.

Thanks!

Matt

Comment on Ways to control a map() operation
Re: Ways to control a map() operation
by chromatic (Archbishop) on Jul 15, 2006 at 20:11 UTC

    map and grep aren't flow control operations. They process each element of a list and return a list.

    Instead of exiting a map or grep block, return an empty list. (You might also use a function from List::Utils instead, such as first().)

      List::Util::first gives you something akin to last, but it’s only a replacement for some uses of grep, and there’s nothing to give you something like next.

      Makeshifts last the longest.

Re: Ways to control a map() operation
by betterworld (Deacon) on Jul 15, 2006 at 20:23 UTC
    last cannot be used to exit a block which returns a value such as eval {} , sub {} or do {}
    This is a bit ambigous. As a matter of fact, you can leave eval{}, sub{} or do{} using last. Have a look at this code:
    sub foo { last; } for (1..3) { print; do { eval { foo (); }; }; }
    It will print only "1". This is because the "last" will jump out of the for-loop, regardless of the surrounding sub{}, eval{} and do{}.

      You really cannot leave eval{} with a last.

      last will exit a loop block, and eval {} is not a loop block. Your example works here because you're cheating :) you placed a loop block around the eval, which is what last is exiting, and not the eval itself.

      Compare your version with

      eval { print "1\n"; last; print "2\n"; };

      Seems to work you say? Take a closer look.

      eval { print "1\n"; last; print "2\n"; }; print "Oops: $@\n" if $@;

      Aha!!! the only reason we exit from the eval is because we are dying, and not because last allows you to exit eval. You may argue that it's exiting in either way, but that's not a good solution since you lose the advantages of eval that way.

      --
      Leviathan.
        You really cannot leave eval{} with a last.
        Oh yes, you can. But you will not only leave the eval but the loop around it, too. If there is no loop around that eval, you'll die—just like your code demonstrated.
        eval { last; }; warn $@ || 'no error'; for (1) { eval { last; }; } warn $@ || 'no error';
        The output of the above code is:
        Can't "last" outside a loop block at x.pl line 2. no error at x.pl line 11.
        By the way, even if the code in my previous post had died in the eval{}, the loop would have continued and the output would have been 123.

        (++ for you anyway, because you almost tricked me into admitting that you're right :)

      This is a bit ambigous. As a matter of fact, you can leave eval{}, sub{} or do{} using last.

      This is plain wrong. please wait for my update before answering.. thanks

      perl -le 'sub leave { last }; leave(1)' Can't "last" outside a loop block at -e line 1. perl -le '$foo = do { last }' Can't "last" outside a loop block at -e line 1. perl -le '$foo = eval { last if 1; 2; }; print $@ if $@; print ">$foo< +"' Can't "last" outside a loop block at -e line 1. ><
      Rather, you can combine eval, sub and last in some obscure way which will look like it's right and perl didn't complain (although it does).

      update: last exits the loop which is around such blocks immediately, thus works like a goto and is likely to leave a mess behind.

      --shmem

      _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                    /\_¯/(q    /
      ----------------------------  \__(m.====·.(_("always off the crowd"))."·
      ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
Re: Ways to control a map() operation
by ioannis (Curate) on Jul 15, 2006 at 20:27 UTC

    You can exit map() with a return, like this: sub{ map { print ; return if 5 >= $_ ; $_ } @arr; }->();

    but in doing so, now you can no longer store the map-derived values to a named array, you will have to push them:

    # This is no good, values are not stored in @early: sub{ @early = map { print ; return if 5 >= $_ ; $_ } 3..9; }->();

    # This is how to store values into @early : sub{ map { print ; return if $_ == 5 ; push @early, $_ } 3..9; }->(); Here is the full snipplet:

    my @arr = 3..9 ; our @early; sub{ map { print ; return if $_ == 5 ; push @early, $_ } @arr; }->(); print "@early";

      What’s the point? Just use a foreach already.

      my @arr = 3..9 ; our @early; sub{ for( @arr ) { print; return if $_ == 5; push @early, $_; } }->(); print "@early";

      Of course, that’s just an obfuscated way of writing the following:

      my @arr = 3..9 ; our @early; for( @arr ) { print; last if $_ == 5; push @early, $_; } print "@early";

      So no, there’s no sensible way to abort a map early.

      Makeshifts last the longest.

Re: Ways to control a map() operation
by liverpole (Monsignor) on Jul 15, 2006 at 20:28 UTC
    Hi Booger,

    You can use grep and map to achieve a shorter, terser form of a loop, if that's what you want to do.  One reason I like map is that it feels more natural to put the entirety of a map block on a single line.  This may or not be want you want, depending on your taste, how clear you want the code to be, or a variety of other factors.

    Let's say you have a simple array of numbers and want to pull all the odd numbers out of it into a separate array.  You can do this in a variety of ways:

    use strict; use warnings; my @nums = ( 1, 2, 3, 8, 19, 31, 42, 77, 113, 121, 144 ); my @odds; # 1. Get odd numbers using "for" for (my $i = 0; $i < @nums; $i++) { if (0 != $num[$i] % 2) { push @odds, $_; } } # 2. Get odd numbers using "foreach" foreach (@nums) { ($_ % 2) and push @odds, $_; } # 3. Get odds using "grep" @odds = grep { 0 != ($_ % 2) } @nums; # 4. Get odds using "map" @odds = map { ($_ % 2)? $_: () } @nums; # 5. Demonstrate the folly of breaking out of "map" with "last" @odds = map { ($_ % 2)? $_: last } @nums;

    In #1, a for loop is used to iterate through the array, pushing each value onto the array odd only if it's odd.

    In #2, a foreach loop is used to do the same thing.  Either of #1 or #2 could be short-circuited in the middle of the loop with last; for example, you could use last to stop at the first odd value.

    In #3, a grep is used to achieve the same results; creating the array odd.

    In #4, using a map demonstrates that map is very similar to a grep.  Notice how cleanly #3 and #4 fit on one line.  You could do the same with a foreach:

    foreach (@nums) { ($_ % 2) and push @odds, $_ }

    but the convention is to use separate lines instead, partially because it's more verbose, and might be considered easier to read or add comments to (because of the extra whitespace).

    Finally, #5 above shows why you can't short-circuit using last; it's explicitly forbidden:

    Can't "last" outside a loop block at last_test.pl line 27.
    While you may or may not agree that it should be legal to do, you now know that, if you *have* to short-circuit, you should stick to one of the other types of loop instead.

    s''(q.S:$/9=(T1';s;(..)(..);$..=substr+crypt($1,$2),2,3;eg;print$..$/
Re: Ways to control a map() operation
by planetscape (Canon) on Jul 16, 2006 at 07:26 UTC

    It may be helpful for you to browse our fine collection of Tutorials. I direct your attention to Map: The Basics, in particular. :-)

    HTH,

    planetscape
Re: Ways to control a map() operation
by shmem (Canon) on Jul 16, 2006 at 12:27 UTC
    Does anyone have any explanation as to why I cannot/should not use last to exit a grep() or map() operation?

    As chromatic said, these are not subject to flow control. And perl tells you, e.g. map:

    perl -le '@l = 1..5; @s = map{last if $_==4;++$_} @l;print "@s"' Can't "last" outside a loop block at -e line 1. perl -le '@l = 1..5; @s = map{return if $_==4;++$_} @l;print "@s"' Can't return outside a subroutine at -e line 1.

    So, map's block is neither a loop block nor a sub. map is a perl function which has its own internal looping mechanism which doesn't set up the context which last needs to operate properly. It's possible to exit an outer loop from inside a map but this prevents map from doing anything useful, since the loop is exited before the return value of map can be assigned to anything. Consider:

    perl -le 'for(1) {@l = 1..5; @s = map {last if $_==4;++$_} @l} print " +@s"'

    The array @s is empty after the for loop. But the half-way through map still returns something:

    perl -le '@n = do { {@l = 1..5; @s = map {last if $_==4; "${_}a"} @l} +}; print "qw(@s)\nqw(@n)"' qw() qw(1a 2a 3a 4 5)
    You should not use such constructs because it isn't quite clear from first glance what is happening here, nor whether it's doing the right thing for you. The above snippet returns the first three elements of @l as modified by the map and the remaining non-processed elements of @l. Did I want this or just the processed elements?

    --shmem

    _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                  /\_¯/(q    /
    ----------------------------  \__(m.====·.(_("always off the crowd"))."·
    ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://561475]
Approved by GrandFather
Front-paged by bart
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others cooling their heels in the Monastery: (6)
As of 2014-04-17 03:49 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    April first is:







    Results (439 votes), past polls