Re: Map Function Weirdness
by jethro (Monsignor) on Nov 08, 2011 at 20:08 UTC
|
#!/usr/bin/perl
use strict;
use warnings;
my %hash=(1=>10,2=>20,3=>30,4=>40,5=>50);
my $max=0;
my @out=map{
$_=$hash{$_};
if($_>$max){$max=$_} else { ()}
# $_;
}keys %hash;
print "Max value was $max\n";
use Data::Dumper; print Dumper \@out;
# prints
Max value was 50
$VAR1 = [
40,
50
];
Note that the result of a block is the result of the last statement of a block. In case of an if-clause that might be the false value, in string context the empty string ''
PS: What for you is strangeness is for most of us a script working as it should. If you don't tell us what you would expect as output we can't tell you where your expectation is wrong
| [reply] [d/l] |
|
|
| [reply] |
Re: Map Function Weirdness
by AnomalousMonk (Archbishop) on Nov 08, 2011 at 20:13 UTC
|
I, also, am a bit unsure of the real question of rthawkcom.
However, if it has to do with the fact that the @out array has all the values of the %hash when $_; is the last statement executed in the map block and has a few empty strings in place of hash values when $_; is not the last statement, I think I can explain.
The value of the map block on each iteration will be the value of the last expression evaluated in the block. This is always the $_ expression, previously set to a hash value, when the $_; statement is last.
When the
if($_>$max){$max=$_}
statement is last, $_>$max will be the last expression evaluated when $_ is less than or equal to $max (and it will evaluate to the empty string, or false).
When $_ is greater than $max the $max=$_ true clause of the statement will execute, which evaluates to $_ previously set to a hash value.
Update: This reply is essentially the same as jethro's.
| [reply] [d/l] [select] |
|
|
$ perl -wE'say 0 + (0 > 1)'
0
$ perl -wE'say 0 + ""'
Argument "" isn't numeric in addition (+) at -e line 1.
0
which evaluates to $_ previously set to a hash value
Not quite. Scalar assignment evaluates to its LHS, so it evaluates to $max, not $_.
$ perl -E'$_ = 123; ++( $max = $_ ); say $max; say $_;'
124
123
Of course, they hold the same value at that point, and map promptly copies that value.
| [reply] [d/l] [select] |
|
|
It is, but easier to follow. I voted you both up. Thanks for the fast help!
| [reply] |
Re: Map Function Weirdness
by ikegami (Patriarch) on Nov 08, 2011 at 20:36 UTC
|
The manual says map "Evaluates the BLOCK or EXPR for each element of LIST (locally setting $_ to each element) and returns the list value composed of the results of each such evaluation."
A block evaluates to what its last statement evaluates, so the real question is: "What does if evaluate to?"
I can't find that in the manual, and that's because if was never meant to be used as an expression. The value to which if evaluates is a side-effect of implementation more than anything else.
if ( EXPR ) BLOCK
is implemented very similarly to
do { ( EXPR ) and do BLOCK };
as seen below:
$ perl -MO=Concise,-exec -e'if (f()) { g() }'
1 <0> enter
2 <;> nextstate(main 3 -e:1) v:{
3 <0> pushmark s
4 <#> gv[*f] s/EARLYCV
5 <1> entersub[t3] sKS/TARG,1
6 <|> and(other->7) vK/1
7 <0> pushmark s
8 <#> gv[*g] s/EARLYCV
9 <1> entersub[t6] vKS/TARG,1
a <@> leave[1 ref] vKP/REFC
-e syntax OK
$ perl -MO=Concise,-exec -e'do { (f()) and do { g() } };'
1 <0> enter
2 <;> nextstate(main 3 -e:1) v:{
3 <0> pushmark s
4 <#> gv[*f] s/EARLYCV
5 <1> entersub[t3] sKPS/TARG,1
6 <|> and(other->7) vK/1
7 <0> pushmark s
8 <#> gv[*g] s/EARLYCV
9 <1> entersub[t6] vKS/TARG,1
a <@> leave[1 ref] vKP/REFC
-e syntax OK
That means that if evaluates to the same thing as its condition if the condition evaluates to something false, and if evaluates to the same thing as its body otherwise.
In your code, it returns the result of the comparion (sv_false, which holds triplevar(0, 0.0, "")) or the result of the assignment ($max, which is currently equal to $_).
| [reply] [d/l] [select] |
|
|
"That means that if evaluates to the same thing as its condition if the condition evaluates to something false, and if evaluates to the same thing as its body otherwise."
For what it's worth, B::Deparse has this to say about it:
perl -MO=Deparse,-x9 -e 'if($a<$b){$a;}'
$a < $b and do {
$a
};
Looking at that makes a lot of sense: and being a logical short circuit means that if $a < $b evaluates to false, the right hand side is never evaluated. Consequently, the return value has to be the value of the relational expression.
On the other hand, if $a is less than $b, the conditional is true, and the logical and allows the right hand side to be evaluated.
if( $a < $b ) { $a }
$a < $b and do { $a };
Two ways of looking at it. The latter seems to me to make a lot of sense.
| [reply] [d/l] [select] |
|
|
>perl -MO=Deparse,-x9 -e"if(my $x=$a<$b){$a;}"
my $x = $a < $b and do {
$a
};
-e syntax OK
The $x is scoped to the if. Or is it...
My code is an approximation too. The if is equivalent to neither
do { ( $a < $b ) and do { $a } };
nor
( $a < $b ) and do { $a };
The my is lexically scoped at compile-time (as shown in mine), but it's not lexically scoped at run-time (as shown by Concise and Deparse). This means the objects can live a little longer than expected.
...
{
...
if (my $obj = ...) {
...
}
.
. # $obj still exists here "anonymously".
.
} # $obj cleared here, so destructor only called here.
...
| [reply] [d/l] [select] |
Re: Map Function Weirdness
by davido (Cardinal) on Nov 09, 2011 at 06:35 UTC
|
"WTF!?"
I love what Mark Jason Dominus said on the subject of questions where something didn't work: "But it does work. It just doesn't do what you wanted. There is a difference, you know." So let's examine why it didn't work as you expected in your situation.
The results you are getting are entirely predictable, and entirely consistent with how Perl evaluates expressions dealing with truth/falsehood. The behavior you're seeing would hold true for subroutines, do{} blocks, and anywhere else where a chain of events could force you to read a few lines higher in the code to find the final expression that got evaluated. This is actually one reason why Damian Conway, in Perl Best Practices suggests that all subroutines should invoke return; to reduce the potential for astonishment when the final expression to get evaluated happens to not be what the programmer originally considered.
Here's what's happening with your snippet:
Let's take the easiest question first: Why does the behavior "do what you want" when you uncomment the final line of the map statement: "# $_;". Why? Because map returns the value of the last statement it evaluates. If the last statement is $_;, on each iteration it returns what's in $_.
Now let's take the next easiest situation: The situation where $_ is greater than $max. In those cases, the last statement that gets evaluated (assuming you comment out the "$_;" line) is this: "$max=$_", which evaluates to whatever value was in $_, which happens to be what your program currently thinks is the maximum value. On the first iteration that's 40. Later on, that's 50.
Now we'll look at the hardest concept: What happens when $_ is less than $max? In that case, your conditional is false, and the block, "{$max=$_}" doesn't get evaluated. So what was the last thing to get evaluated? "$_>$max", and it evaluated to false.
The final thing to get evaluated on iteration two, three, and four was a conditional statement that evaluated to false. That's what got returned via map on that iteration.
It may have been made a little more confusing by virtue of the fact that hashes have no implicit order. You take what you get, essentially. It turns out that on the first iteration, the value that is being iterated over is '40'. On the 2nd, 3rd, and 4th, it's some value less than 40 (which is why your if() conditional evaluates to false). On the final 5th iteration, it the iterator happens to fall on the hash value of 50. The conditional evaluates to true once again, $_ sets $max, and that value gets returned rather than the boolean value of the conditional within the if() statement.
This is covered in the manual:
map: "Evaluates the BLOCK or EXPR for each element of LIST (locally setting $_ to each element) and returns the list value composed of the results of each such evaluation."
(Hint, figure out what expression was ultimately evaluated in each iteration.)
perlsyn#Truth and Falsehood: Truth and Falsehood
The number 0, the strings '0' and '' , the empty list () , and undef are all false in a boolean context. All other values are true.
(Hint: The last expression to be evaluated was false for iterations 2, 3, and 4).
If you want some painstakingly boring proof that Perl was just doing what you asked it to do, and that your associating the issue with map weirdness was overlooking all of the other similar situations where it may not seem so weird yet is 100% consistent, read on:
| [reply] [d/l] [select] |
Re: Map Function Weirdness
by toolic (Bishop) on Nov 08, 2011 at 19:48 UTC
|
Not sure what your point is, but see also values and List::Util (if you don't need the intermediate array)...
use strict;
use warnings;
use List::Util qw(max);
my %hash = (1=>10,2=>20,3=>30,4=>40,5=>50);
my $max = max(values %hash);
print "Max value was $max\n";
__END__
Max value was 50
| [reply] [d/l] |
|
|
The intermediate array is needed. This is just a simplified version of the actual code to demonstrate the strangeness.
| [reply] |
Re: Map Function Weirdness
by ~~David~~ (Hermit) on Nov 08, 2011 at 19:41 UTC
|
Read up on the map function.
Evaluates the BLOCK or EXPR for each element of LIST (locally setting
+$_ to each element) and returns the list value composed of the result
+s of each such evaluation.
In scalar context, returns the total number of elements so generated.
Evaluates BLOCK or EXPR in list context, so each element of LIST may p
+roduce zero, one, or more elements in the returned value.
| [reply] [d/l] |
Re: Map Function Weirdness
by Anonymous Monk on Nov 08, 2011 at 19:41 UTC
|
The comment makes no difference here. Would you like to further explain your question, or perhaps show the output that surprised you?
| [reply] |
|
|
| [reply] |
|
|
Yes, I'm sure. They both appear to do the same thing here on 5.12.3. It's probably different for your versions of perl, I understand that. That's why I encouraged you to indicate what the surprising output was and why it surprised you.
If you're actually trying to find the maximum of the values of that hash, then you're going about it in a weird way. Some things you could do to improve would be to:
- don't use map for its side effects when you really want a foreach loop.
- don't loop over the keys when you're actually investigating the values and don't care about the keys.
- investigate the documentation for map. It takes a block or an expression, and evaluates them in list context, placing the results in your @out, so be mindful of what it evaluates to.
- See List::Util
| [reply] |
A reply falls below the community's threshold of quality. You may see it by logging in.
|