为什么这个随机哈希值选择器总是在第一次调用时返回相同的值?

时间:2022-10-02 12:17:14

While trying to understand how refp's solution for random hash value selection works, I noticed something strange.

在尝试理解refp的随机哈希值选择解决方案是如何工作的时候,我发现了一些奇怪的东西。

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:

通过反复调用以下Perl脚本,我始终发现返回的第一个结果是相同的。返回的后续值是随机的:

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).

这已在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?

从表面上看,似乎设置@_与它有关,但我不确定。任何人都可以了解这里发生的事情吗?


EDIT

I thought this question was answered until refp provided an update. Why does the arrayref form not suffer from the same issue discussed above? :

我认为这个问题得到了回答,直到refp提供了更新。为什么arrayref形式没有遇到上面讨论的相同问题? :

[@_=%hash]->[1|rand@_] for 1 .. 10;            # First value is random

2 个解决方案

#1


7  

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.

崩溃和烧毁因为@a未在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.

没有任何不同。在您指定的同一语句中使用变量仍然是不好的做法。我猜,差异在于优先级有所改变,因此首先评估赋值。

#2


4  

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.

在切片列表之前评估列表切片的索引。 (文档不保证这种方式或其他方式。)因此在第一次迭代时,@ _为空,rand的参数为1 | 0(= 0)。从历史上看,rand(0)的行为与rand(1)相似,但现在记录为可以改变。因此,第一次迭代的索引是> = 0且<1,并且通过索引的隐式int取为0。

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.

数组元素提取([@ _ =%hash] - > [1 | rand @ _])不会遇到类似的问题,因为它会在数组操作数之后计算索引。另一方面,数组切片(@ {[@ _ =%hash]} [1 | rand @ _])的行为与列表切片相同。

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

#1


7  

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.

崩溃和烧毁因为@a未在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.

没有任何不同。在您指定的同一语句中使用变量仍然是不好的做法。我猜,差异在于优先级有所改变,因此首先评估赋值。

#2


4  

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.

在切片列表之前评估列表切片的索引。 (文档不保证这种方式或其他方式。)因此在第一次迭代时,@ _为空,rand的参数为1 | 0(= 0)。从历史上看,rand(0)的行为与rand(1)相似,但现在记录为可以改变。因此,第一次迭代的索引是> = 0且<1,并且通过索引的隐式int取为0。

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.

数组元素提取([@ _ =%hash] - > [1 | rand @ _])不会遇到类似的问题,因为它会在数组操作数之后计算索引。另一方面,数组切片(@ {[@ _ =%hash]} [1 | rand @ _])的行为与列表切片相同。

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