### Short-circuiting a map list.

by BrowserUk (Pope)
 on Oct 07, 2011 at 22:45 UTC Need Help??
BrowserUk has asked for the wisdom of the Perl Monks concerning the following question:

Is there any way to terminate a map statement before the end of the input list?

Notionally:

```@out = map{ \$_ == \$target and last } @in;

The map in question would be embedded in a subroutine, so something like:

```sub x{
my \$target = shift;
map {
return if \$_ == \$target;
} @_;
}

print for x( 5, 1 ,, 9 );
0
1
2
3
4

Yes, I am aware there are other ways of achieving that goal, but it is only an easily understood example of a generic class of problems.

Note. The desire is not just to not return anything more after the condition is met, but to not inspect further either.

Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
"Science is about questioning the status quo. Questioning authority".
In the absence of evidence, opinion is indistinguishable from prejudice.

Replies are listed 'Best First'.
Re: Short-circuiting a map list.
by ikegami (Pope) on Oct 08, 2011 at 01:24 UTC
```package Mapx;

use strict;
use warnings;

use Exporter qw( import );
our @EXPORT = qw( mapx DONE_MAP );

use Scalar::Util qw( reftype blessed );

use constant DONE_MAP => \&mapx;

sub mapx(&@) {
my \$cb = shift;
my @rv;
for (@_) {
my @x = \$cb->();
last if @x == 1 && reftype(\$x[0]) && !blessed(\$x[0]) && \$x[0] ==
+ DONE_MAP;
push @rv, @x;
}
return @rv;
}

1;
```use strict;
use warnings;
use 5.010;

use Mapx;

sub x {
my \$target = shift;
mapx {
return DONE_MAP if \$_ == \$target;
\$_
} @_;
}

say for x( 5, 1..9 );
Re: Short-circuiting a map list.
by moritz (Cardinal) on Oct 08, 2011 at 05:04 UTC

FWIW map in Perl 6 supports last

```\$ perl6 -e 'my @out = (1..5).map: { \$_ == 3 and last }; say @out.perl'
Array.new(False, False)
Earlier, I tried using last from a map reimplementation (since it's possible to exit a sub using last) but some AV was getting left on the stack (bizarre copy of array).
Re: Short-circuiting a map list.
by roboticus (Chancellor) on Oct 07, 2011 at 23:30 UTC

Is this anything close? I'm not using map, and it's not pretty when dealing with undefined values, but it stops evaluating list arguments when it hits the match value. Perhaps it could be massaged into something you want.

```\$ cat 930256_a.pl
#!/usr/bin/perl
use strict;
use warnings;

#my @list = (5, undef, 1 .. 9);
my @list = (5, 1 .. 9);

sub xmap {
my \$match = shift;
my \$rc = shift;
my @rv=();
for (@_) {
push @rv, &\$rc(\$_);
last if \$_ eq \$match;
}
return @rv;
}

print "list: ", join(", ", @list), ".\n";
print "xmap: ", join(", ", xmap(7, sub { \$_*2 }, @list)),".\n";

\$ perl 930256_a.pl
list: 5, 1, 2, 3, 4, 5, 6, 7, 8, 9.
xmap: 10, 2, 4, 6, 8, 10, 12, 14.

...roboticus

When your only tool is a hammer, all problems look like your thumb.

Is this anything close?

No, not even close. The question is about short-circuiting map, not solving a toy problem (and clearly identified as such) used to demonstrate the requirements.

Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
"Science is about questioning the status quo. Questioning authority".
In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Short-circuiting a map list.
by davido (Archbishop) on Oct 08, 2011 at 08:53 UTC

This doesn't give you "return()", but it does give you the following short-circuiting alternatives, two of which can still return a list:

```bnext # terminates current iteration without any 'push', but a list
# will still be returned if other iterations resulted
# in a push.

blast # terminates loop.  Any 'push'es from previous
# iterations are retained in results.

bbail # Bails out with no result set (returns () )

The need for multiple return points can be mostly resolved with next and last, and of course with good old structured programming techniques. Here's the module implementing bmap{}(); a map that allows last, next, and bail:

```package Bmap;

use strict;
use warnings;

use Exporter qw/ import /;

our @EXPORT = qw/bmap bnext blast bbail/;

sub bnext() {
no warnings 'exiting';
next;
}

sub blast() {
no warnings 'exiting';
last;
}

sub bbail() {
goto BBAIL;
}

sub bmap(&@) {
my \$f = shift;
my @return_value;
for ( @_ ) {
my @iteration_results = \$f->();
push @return_value, @iteration_results;
}
return @return_value;
BBAIL: return ();
}

1;

And now here is some code to test it. Notice the first example passes over 'and' in the list. The second example passes over 'and', and everything that came after it. And the third example bails out with ().

```use strict;
use warnings;
use v5.12;

use Bmap;

my @array = qw/ this that and the other /;

my @nexted = bmap{ \$_ eq 'and' && bnext(); \$_ } @array;
my @lasted = bmap{ \$_ eq 'and' && blast(); \$_ } @array;
my @bailed = bmap{ \$_ eq 'and' && bbail(); \$_ } @array;

say "'next' version: @nexted";
say "'last' version: @lasted";
say "'bail' version: @bailed";

And the test run:

```'next' version: this that the other
'last' version: this that
'bail' version:

The hard part was coming up with a solution that would squelch the "exiting" warning. At first my implementation didn't create its own "next" and "last" functions, and that meant there was nowhere I could put the no warnings that would actually propagate out to the necessary scope without cluttering the bmap's usage.

...no good reason for the function's name...

This may fall short somehow. But I'm interested in your thoughts on this approach.

First (and maybe last) time I've used the unpopular version of goto non-jokingly. *shudder*

Dave

Re: Short-circuiting a map list.
by ikegami (Pope) on Oct 07, 2011 at 23:10 UTC
I don't see how short of replacing map. Any exceptional flow would lose already "returned" values.
Re: Short-circuiting a map list.
by RichardK (Parson) on Oct 08, 2011 at 12:43 UTC

Could you get the result you need using List::MoreUtils before or before_incl?

So something like :-

```@results = before { \$_ == \$target} @list;

Of course if you needed to transform the values as well you'd have to map that output

```@results = map { transform(\$_) } before {\$_ == \$target} @list;
Re: Short-circuiting a map list. (This works! But ...)
by BrowserUk (Pope) on Oct 08, 2011 at 14:50 UTC

If you look at the output below, the while loop is entered ('a') just one at the top.

The map is entered ('b') just 6 times as required.

If you look at the bottom, of the output, 'just' the required values are returned.

There is just the minor irritation of the weirdness marked '#??'.

```sub a{
my \$x = shift;

while(1){
print 'a';
map {
print 'b';
last if \$_ == \$x;
\$_;
} @_;
print 'c';
};
}

my @data = 0 .. 9;

print for a( 5, @data );

__END__
c:\test>junk
a
b
b
b
b
b
b
0 ##??
1 ##??
2 ##??
3 ##??
4 ##??
5 ##??
6 ##??
7 ##??
8 ##??
9 ##??
0
1
2
3
4

I guess I was hoping for some of the legendary PM inventiveness?

Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
"Science is about questioning the status quo. Questioning authority".
In the absence of evidence, opinion is indistinguishable from prejudice.

In 5.12.2 I get as you, 0,1,2,3,4,5,6,7,8,9, with 0,1,2,3,4

If I explicitly add return map{} I get only 0,1,2,3,4,5,6,7,8,9, without 0,1,2,3,4

In perl 5.6 I get 0,1,2,3,4,5,6,7,8,9, without 0,1,2,3,4

In perl 5.14.1 I get 0,1,2,3,4,5,6,7,8,9, BUT with 0,0,0,0

perlsub says: If no return is found and if the last statement is an expression, its value is returned. If the last statement is a loop control structure like a foreach or a while , the returned value is unspecified.

Okay. If what you are telling me is that I am utilising unspecified behaviour, then how about this version which produces identical output but, as far as I can tell, does nothing that is either prohibited or unspecified?

```sub a{
my \$x = shift;

return do{
{
print 'a';
map {
print 'b';
last if \$_ == \$x;
\$_;
} @_;
print 'c';
}
};
}

my @data = 0 .. 9;

print for a( 5, @data );

Does that mean uncovered an obscure bug?

Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
"Science is about questioning the status quo. Questioning authority".
In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Short-circuiting a map list.
by Jim (Curate) on Oct 08, 2011 at 00:24 UTC

Language::Functional::any(), perhaps…

```#!perl

use strict;
use warnings;
use feature qw( say );

use Language::Functional qw( any );

sub is_even { \$_[0] % 2 == 0 }

say any { is_even(shift) } [ 1, 3, 5, 7 ]; # Prints 0
say any { is_even(shift) } [ 2, 4, 6, 8 ]; # Prints 1

exit 0;

(Does List::MoreUtils::any() short-circuit evaluate its list? I don't know, but I would presume it does.)

How do two functions which both return true or false, return the input list until a condition is met?

Re: Short-circuiting a map list.
by Anonymous Monk on Oct 08, 2011 at 10:19 UTC

I propose, until the real map supports short circuiting, instead of mapx or bmap, we call it mapuntil :)

I agree; bmap is a bad name. 'break map' is what I saw as its full name when I came up with it. It's not really a 'map until' though. Maybe a map with short circuits, so an 'scmap'. Is that any worse than the very familiar but functionally ambiguous names, "sprintf", "printf", "grep", "chop" and "chomp"? ;)

It's unfortunate that there just isn't a nice concise way to clearly summarize every function's purpose with a simple name. If we called it map_with_next_last_and_bail_short_circuits I don't think it would ever gain any measure of acceptance. It may not, even with a concise name, but at least the name wouldn't be the obstacle.

Dave

Create A New User
Node Status?
node history
Node Type: perlquestion [id://930256]
Approved by GrandFather
help
Chatterbox?
and the questions are moot...

How do I use this? | Other CB clients
Other Users?
Others perusing the Monastery: (2)
As of 2018-03-24 06:54 GMT
Sections?
Information?
Find Nodes?
Leftovers?
Voting Booth?
When I think of a mole I think of:

Results (297 votes). Check out past polls.

Notices?