Re: Assign result of map to scalar variable
by runrig (Abbot) on Jul 18, 2013 at 20:30 UTC
|
Capturing parens return an array of the results, and you only have one set of capturing parens, so you can just return that: my ($val) = map { /^\*\s\(?([^\)]+)\)?/} @GET_STRING;
# Or (as others have suggested)
my ($val) = map { /^\*\s\(?([^\)]+)\)?/ ? $1 : () } @GET_STRING;
| [reply] [d/l] |
|
|
| [reply] [d/l] |
Re: Assign result of map to scalar variable
by toolic (Bishop) on Jul 18, 2013 at 19:12 UTC
|
UPDATE: Ugly and not recommended...
my $VAL = (grep { defined } map { /^\*\s\(?([^\)]+)\)?/ ? $1 : () } @G
+ET_STRING)[0];
Consider using grep instead of map. | [reply] [d/l] |
|
|
>perl -wMstrict -le
"my @GET_STRING =
('no', '*nine', '*(not)', '* yes', '* (yup)', '* ya)');
;;
my $first =
(map { / ^ \* \s \(? ([^\)]+) \)? /x ? $1 : () } @GET_STRING)[0];
print qq{'$first'};
"
'yes'
| [reply] [d/l] [select] |
|
|
| [reply] [d/l] |
|
|
| [reply] [d/l] |
Re: Assign result of map to scalar variable
by AnomalousMonk (Archbishop) on Jul 18, 2013 at 19:52 UTC
|
... only one element will be the output of map.
Not so. As given in the OPed code, map will output a value for each input value it receives. If there is a match, $1 is returned. If the match fails, the value of the last expression evaluated, the regex itself, will be the output, and this output will be '' (the empty string, i.e., false) because the match failed. See example below in which the entire output of map is captured and displayed. The print version may have seemed to work because print will by default (depends on value of $, – see perlvar) separate printed items with an empty string, and any empty strings from map will be collapsed to nothing.
>perl -wMstrict -le
"use Data::Dump;
;;
my @GET_STRING =
('no', '*nine', '*(not)', '* yes', '* (yup)', '* ya)');
;;
my @ra = map { $1 if / ^ \* \s \(? ([^\)]+) \)? /x } @GET_STRING;
dd \@ra;
"
["", "", "", "yes", "yup", "ya"]
A variation using map could be made to work as I think you want as follows:
>perl -wMstrict -le
"my @GET_STRING =
('no', '*nine', '*(not)', '* yes', '* (yup)', '* ya)');
;;
my ($first) =
map { / ^ \* \s \(? ([^\)]+) \)? /x ? $1 : () } @GET_STRING;
print qq{'$first'};
"
'yes'
Update: HOWEVER: A solution using map may not be the most efficient. map will always process the entire array, but you seem to want to capture only the first match; this can easily be done with a for-loop that would also allow you to terminate matching after the first match.
Update: In the second example above, $first will always be defined except if there was no matching string in the @GET_STRING array — but in that case, I think one could argue that $first should be undefined!
Update: All those 'nine's should really have been 'nein' == German 'no'. (blush)
| [reply] [d/l] [select] |
|
|
... map will output a value for each input value it receives. If there is a match, $1 is returned. If the match fails, the value of the last expression evaluated
Not if you return an empty list (). The usual idiom is this:
my @map_some = map { /foo(bar)baz/ ? $1 : () } @all;
(However, I'm apparently having a Copy/Paste-challenged day, so if you read my node within the first few minutes, it still would have had the OP's if conditional rather than the one I tested.)
| [reply] [d/l] [select] |
|
|
| [reply] [d/l] |
Re: Assign result of map to scalar variable
by rjt (Curate) on Jul 18, 2013 at 19:46 UTC
|
my ($val) = map { /^\*\s\(?([^\)]+)\)?/ ? $1 : () } @GET_STRING;
| [reply] [d/l] [select] |
|
|
You just need parentheses around the LHS to force list context...
(Note: In the code originally replied to, the map block statement was $1 if /^\*\s\(?([^\)]+)\)?/) (tag – you're it)
But that would only work if the first item in @GET_STRING matched the regex. (I'm assuming this array may contain any number of matching or non-matching strings in any order.) If it did not, an empty string would be assigned to the scalar. See first example below.
Update: Also:
>perl -wMstrict -le
"my @GET_STRING =
('no', '*nine', '*(not)', '* yes', '* (yup)', '* ya)');
;;
my ($first) =
map { $1 if / ^ \* \s \(? ([^\)]+) \)? /x } @GET_STRING;
print qq{'$first'};
"
''
| [reply] [d/l] [select] |
Re: Assign result of map to scalar variable
by hdb (Monsignor) on Jul 18, 2013 at 20:34 UTC
|
$VAL = join '', map { /^\*\s\(?([^\)]+)\)?/ ? $1 : '' } @GET_STRING;
| [reply] [d/l] |
|
|
$VAL = join '', map { /^\*\s\(?([^\)]+)\)?/ ? $1 : '' } @GET_STRING;
It's not clear to me from the OP, but I'm assuming the @GET_STRING array may contain any number of matching or non-matching strings in any order. If this is so, a join solution such as you have would not work:
>perl -wMstrict -le
"my @GET_STRING =
('no', '*nine', '*(not)', '* yes', '* (yup)', '* ya)');
;;
my $VAL = join '', map { /^\*\s\(?([^\)]+)\)?/ ? $1 : '' }@GET_STRING
+;
print qq{'$VAL'};
"
'yesyupya'
| [reply] [d/l] [select] |
|
|
My assumption was that there is only one match based on the OP's comment that print would work ok and that he wanted to assign to scalar as map would only return one value. If this assumption is incorrect, then one needs to change the approach.
In order to clarify some of the arguments in the thread and based on the assumption of only one match, please have a look at the following list of experiments:
use strict;
use warnings;
my @match = map { /(2)/ ? $1 : () } 0..9;
print scalar @match, "\n"; # is 1, no grep required
@match = map { /(2)/ } 0..9;
print scalar @match, "\n"; # is 1
@match = map { $1 if /(2)/ } 0..9;
print scalar @match, "\n"; # is 10
@match = grep { defined } map { $1 if /(2)/ } 0..9;
print scalar @match, "\n"; # is 10
@match = grep { length } map { $1 if /(2)/ } 0..9;
print scalar @match, "\n"; # is 1
From this I would conclude that the easiest-to-write version for the OP is indeed
my ($val) = map { /^\*\s\(?([^\)]+)\)?/ } @GET_STRING;
not considering any performance issues. If there is more than one match, this just returns the first one, which might or might not be ok.
The whole discussion highlights that sometimes the best thing to do might be to use a for loop and be done with it. Probably the fastest solution here as well depending on the data:
my $val;
for (0..9) {
if( /(2)/ ) {
$val = $1;
last;
}
}
| [reply] [d/l] [select] |
|
|
|
|
Re: Assign result of map to scalar variable
by Laurent_R (Canon) on Jul 18, 2013 at 22:13 UTC
|
Don't use map if you need to check only the first element of the array, use shift.
Just a couple of examples under the Perl debugger:
DB<1> print map {$1 if /oo/} qw /foo bar oof rab foo/
DB<2> print map {$1 if /(oo)/} qw /foo bar oof rab foo/
oooooo
DB<3> @c = map {$1 if /(oo)/} qw /foo bar oof rab foo/
DB<4> p "@c"
oo oo oo
DB<5> ($d) = map {$1 if /(oo)/} qw /foo bar oof rab foo/
DB<6> p $d
oo
DB<7> ($d) = map {$1 if /(ar)/} qw /foo bar oof rab foo/
DB<8> p $d
DB<9> ($d) = map {$1 if /(ar)/} qw /foo bar oof rab foo/
DB<10> p $d
This was just to show, in the event that there was any doubt, that if the regex fails on the first value, then the scalar is undef the empty string, even if the regex in the map code block succeeds on subsequent values. In other words, everything is exactly as if you applied the regex only on the first element of the array. But if the array has millions of entries, the map will still do the work on each entries of the array, whereas we are interested only on the regex match on the first element of the array. The fact that we are keeping only one element returned by map does not lead to a lazy evaluation of map (at least not on my Perl version, i.e. 5.14), as you can see from these one-liners:
$ time perl -e '($d) = map {$1 if /(000)/} 1..999;'
real 0m0.070s
user 0m0.031s
sys 0m0.046s
$ time perl -e '($d) = map {$1 if /(000)/} 1..9999999;'
real 0m18.116s
user 0m17.674s
sys 0m0.436s
Obviously, map is doing a lot of useless work here. Therefore, I would recommend to shift the first value of the array and apply the desired regex on it. It's much much more efficient if the array is large. It also makes more sense: map is designed to work on lists of value, no point of using it on a single value.
Then, there is also the possibility that the OP wanted to get the first successful match, not the result of the match on the first element. But, as we have seen, map is not suited for that. A map filtered with an appropriate grep would return the first successful match, but we still have the problem of having possibly a lot of unecessary work to go through all the array elements, even if the match occured early. In that case, I would recommand a foreach loop, with a last statement on successful match, to obtain what I consider to be the proper lazy behavior.
Update: corrected the "undef" to "empty string", following the comment by AnomalousMonk. | [reply] [d/l] [select] |
|
|
... if the regex fails on the first value, then the scalar is undef ...
I agree that if you want processing to stop, already, after the first match, a for-loop is the way to go.
However, in the code you're using in your example map blocks, the value assigned to a scalar in list-context in the event of match failure would be the empty string, not undef.
(I should mention that even though the OP is a bit unclear on this point, I assume that the @GET_STRING array may contain any number of matching or non-matching strings in any order.)
| [reply] [d/l] [select] |
|
|
Yes, you are right, AnomalousMonk, it is an empty string, not undef. I made the correction.
| [reply] |