While trying to understand how refp's solution for random hash value selection works, I noticed something strange.
With repeated calls to the following Perl script, I consistently found that the first result returned was the same. The subsequent values returned were random:
use strict;
use warnings;
use 5.010;
my %hash = map { $_ => ord $_ } 'a' .. 'z';
say( (@_=%hash)[1|rand@_] ) for 1 .. 10; # First value always 119
Interestingly, the following does not suffer from this issue:
sub random_value { ( @_ )[ 1 | rand @_ ] }
say random_value %hash for 1 .. 10; # First value is random
Removing the references to @_
also remedies the problem:
say( (%hash)[1|rand keys %hash] ) for 1 .. 10; # First value is random
This has been tested on Windows (ActivePerl 5.14.2).
On the surface, it looks like setting @_
has something to do with it, but I'm not sure. Can anyone shed some light on what's happening here?
I thought this question was answered until refp provided an update. Why does the arrayref form not suffer from the same issue discussed above? :
[@_=%hash]->[1|rand@_] for 1 .. 10; # First value is random
I suspect there is a race condition where @_
is not defined in the first loop iteration.
say( (@_=%hash)[1|rand@_] ) for 1 .. 10;
Will become
say( (@_=%hash)[1|rand ()] ) for 1 .. 10;
It escapes warnings
because @_
is a predeclared variable. As you will notice:
say( (my @a=%hash)[1|rand@a] ) for 1 .. 10;
Will crash and burn because @a
is not defined in the postscript.
Update:
[@_=%hash]->[1|rand@_] for 1 .. 10;
Is not any different. It is still bad practice to use a variable in the same statement that you assign it. The difference, I am guessing, is that the precedence is somewhat altered, so that the assignment is evaluated first.
No race condition, or anything to do with whether @_ is "defined" or not, just an order of operations issue.
The indexes for a list slice are evaluated before the list being sliced. (The documentation doesn't guarantee this one way or the other.) So on the first iteration, @_ is empty and the argument to rand is 1|0 (= 0). Historically, rand(0) has behaved like rand(1), though this is now documented as subject to change. So the index on the first iteration is >= 0 and < 1, and taken to be 0 by the implicit int of indexing.
The array element fetch ([@_=%hash]->[1|rand@_]
) doesn't suffer a similar problem because it evaluates the index after the array operand. An array slice (@{[@_=%hash]}[1|rand@_]
), on the other hand, behaves as the list slice does.
Compare:
List slice:
$ perl -MO=Concise,-exec -e'(@_=%hash)[1|rand@_]'
1 <0> enter
2 <;> nextstate(main 1 -e:1) v:{
3 <0> pushmark s
4 <$> const[IV 1] s
5 <#> gv[*_] s
6 <1> rv2av[t7] sK/1
7 <1> rand[t8] sK/1
8 <2> bit_or[t9] sK
9 <0> pushmark s
a <0> pushmark s
b <#> gv[*hash] s
c <1> rv2hv[t4] lK/1
d <0> pushmark s
e <#> gv[*_] s
f <1> rv2av[t2] lKRM*/1
g <2> aassign[t5] lKS/COMMON
h <2> lslice vK/2
i <@> leave[1 ref] vKP/REFC
-e syntax OK
Array slice:
$ perl -MO=Concise,-exec -e'@{[@_=%hash]}[1|rand@_]'
1 <0> enter
2 <;> nextstate(main 2 -e:1) v:{
3 <0> pushmark s
4 <$> const[IV 1] s
5 <#> gv[*_] s
6 <1> rv2av[t8] sK/1
7 <1> rand[t9] sK/1
8 <2> bit_or[t10] sK
9 <0> pushmark s
a <0> pushmark s
b <#> gv[*hash] s
c <1> rv2hv[t4] lK/1
d <0> pushmark s
e <#> gv[*_] s
f <1> rv2av[t2] lKRM*/1
g <2> aassign[t5] lKS/COMMON
h <@> anonlist sK*/1
i <1> rv2av[t6] sKR/1
j <@> aslice vK
k <@> leave[1 ref] vKP/REFC
-e syntax OK
Array element:
$ perl -MO=Concise,-exec -e'[@_=%hash]->[1|rand@_]'
1 <0> enter
2 <;> nextstate(main 1 -e:1) v:{
3 <0> pushmark s
4 <0> pushmark s
5 <#> gv[*hash] s
6 <1> rv2hv[t4] lK/1
7 <0> pushmark s
8 <#> gv[*_] s
9 <1> rv2av[t2] lKRM*/1
a <2> aassign[t5] lKS/COMMON
b <@> anonlist sK*/1
c <1> rv2av[t10] sKR/1
d <$> const[IV 1] s
e <#> gv[*_] s
f <1> rv2av[t7] sK/1
g <1> rand[t8] sK/1
h <2> bit_or[t9] sK
i <2> aelem vK/2
j <@> leave[1 ref] vKP/REFC
-e syntax OK
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With