起初,我只是想知道为什么此代码第2行中的空括号前有一个冒号(来自Perl 6 Advent Calendar, December 25, 2018)?
sub is-happy( $n is copy ) {
my $seen-numbers = :{};
while $n > 1 {
return False if $n ∈ $seen-numbers;
$seen-numbers{$n} = True;
$n = $n.comb.map(*²).sum
}
return True;
}
say is-happy(7); # True
say is-happy(2018); # False
该代码几乎立即运行。我尝试了say $seen-numbers.^name;
,发现它是Hash[Mu,Any]
。
但是,当我删除冒号时,is-happy(7)
返回True
,但随后is-happy(2018)
将CPU占用了几分钟(直到我杀死了该进程)。
同样,在这种情况下,say $seen-numbers.^name;
打印Hash
。
因此,很显然,:{}
会创建一个Hash[Mu,Any]
。这是如何运作的?这是一种解决方法还是惯用的?什么是Hash[Mu,Any]
?与普通Hash
相比,又如何?
答案 0 :(得分:14)
一个Hash
具有Str
键。非Str
的任何键都将被强制为一个,然后再将值存储在该字符串键下。 {}
构造一个空的Hash
。
可以通过提供Hash
类型参数来更改此行为。例如,Hash[Int]
仍然具有自动强制的字符串键,但是只能存储Int
的值(很像Array[Int]
只能存储Int
的值)。也可以传递第二个类型参数来选择键的类型。这将创建通常称为“对象哈希”的内容,并存储索引器中提供的确切键。因此,Hash[Mu,Any]
是具有不受限制的值和Any
类密钥的哈希(几乎所有类型都属于Any
,但Junction
除外)。密钥的.WHICH
用于散列。
大多数时候,不是使用Hash[TValue,TKey]
创建对象哈希,而是使用声明语法;例如,我可能使用诸如has Array %!edges-from{Mu}
之类的东西来表示DAG中的边。还有一种创建匿名对象哈希:{}
的方法,这是您在示例中观察到的。
那么,它有什么不同?在这里:
$seen-numbers{$n} = True;
$n
将存储为Int
,其.WHICH
用于散列以获取O(1)查找。如果我们只是使用{}
,则将Int
强制转换为密钥的Str
。区别在这里变得很重要:
return False if $n ∈ $seen-numbers;
因为设置的成员资格运算符查找相同的值。因此,当我们有一个对象哈希:{}
存储了Int
时,$n
可能与哈希中的键之一相同;如果是,则算法终止。但是,对于使用{}
创建的普通哈希,密钥是Str
,因此永远不会与Int
相同,因此,设置成员资格检查将始终失败,从而导致未终止算法。
该程序与自己的决定一致。一个人可以这样写:
return False if $seen-numbers{$n}:exists;
然后它将与{}
(Hash
)一起使用。但是,它将强制将这些数字强制存储到Str
中,以便每次存储和查找,而使用:{}
可以避免这种情况。因此,可以说该程序通过使用:{}
做出了更有效的数据结构选择,从而启用了∈
,这被认为更具可读性(特别是如果目标受众是习惯于阅读此类数学运算符的人。
编写程序的另一种可能更清晰的方法是:
my %seen-numbers is SetHash;
while $n > 1 {
return False if $n ∈ %seen-numbers;
%seen-numbers{$n} = True;
$n = $n.comb.map(*²).sum
}
return True;
更清楚的是,我们将%seen-numbers
视为一个集合,尽管在这种情况下,该名称对其用途没有多少疑问。