为什么:{}创建一个“ Hash [Mu,Any]”对象(它是什么以及与普通的“ Hash”相比如何)?

时间:2018-12-27 15:42:17

标签: perl6

起初,我只是想知道为什么此代码第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相比,又如何?

1 个答案:

答案 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视为一个集合,尽管在这种情况下,该名称对其用途没有多少疑问。