将 %hash 和 return 具有相同结构的 %hash 中的所有值相乘

Multiply all values in a %hash and return a %hash with the same structure

我有一些 JSON 存储在数据库列中,如下所示:

pokeapi=# SELECT height FROM pokeapi_pokedex WHERE species = 'Ninetales';
-[ RECORD 1 ]------------------------------------------
height | {"default": {"feet": "6'07\"", "meters": 2.0}}

作为我正在研究的 'generation' 算法的一部分,我想将此值放入 %hash,将其乘以 (0.9..1.1).rand(以允许“自然的 10%”高度差异),然后在相同的结构中创建一个新的 %hash。我的 select-height 方法如下所示:

method select-height(:$species, :$form = 'default') {
    my %heights = $.data-source.get-height(:$species, :$form);

    my %height = %heights * (0.9..1.1).rand;

    say %height;
}

这实际上调用了我的 get-height 例程来获取该物种的 'average' 高度(公制和英制)。

method get-height (:$species, :$form) {
    my $query = dbh.prepare(qq:to/STATEMENT/);
           SELECT height FROM pokeapi_pokedex WHERE species = ?;
        STATEMENT

    $query.execute($species);

    my %height = from-json($query.row);
    my %heights = self.values-or-defaults(%height, $form);

    return %heights;
}

但是我在执行时遇到以下错误(我假设是因为我试图将哈希作为一个整体而不是哈希的各个元素进行倍增):

$ perl6 -I lib/ examples/height-weight.p6
{feet => 6'07", meters => 2}
Odd number of elements found where hash initializer expected:
Only saw: 1.8693857987465123e0
  in method select-height at /home/kane/Projects/kawaii/p6-pokeapi/lib/Pokeapi/Pokemon/Generator.pm6 (Pokeapi::Pokemon::Generator) line 22
  in block <unit> at examples/height-weight.p6 line 7

是否有更简单(且有效)的方法来执行此操作而无需为每个元素重复我的代码? :)

首先,您的代码逻辑有问题。最初,您将获得值的散列,"feet": "6'07\"", "meters": 2.0 从 json 中解析出来,其中 meters 是一个数字,feet 是一个字符串。接下来,您尝试将它乘以一个随机值……虽然它适用于数字,但不适用于字符串。 Perl 6 变体允许你这样做,实际上:say "5" * 3 将 return 15,但是 X"Y' 模式非常复杂,Perl 6 无法自然地理解它。

因此您可能需要在处理之前对其进行转换,然后再将其转换回来。

第二件事是导致您观察到的错误的确切行。

考虑一下:

my %a = a => 5;
%a = %a * 10 => 5; # %a becomes a hash with a single value of 10 => 5
# It happens because when a Hash is used in math ops, its size is used as a value
# Thus, if you have a single value, it'll become 1 * 10, thus 10
# And for %a = a => 1, b => 2; %a * 5 will be evaluated to 10
%a = %a * 10; # error, the key is passed, but not a value

要直接处理哈希值,您需要使用 map 方法并处理每一对,例如:%a .= map({ .key => .value * (0.9..1.1).rand }).

当然可以打打,也可以换个方式写,不过主要问题就这样解决了。

您已接受@Takao 的回答。该解决方案需要手动挖掘 %hash 以到达叶子 hashes/lists,然后应用 map.

鉴于您的问题标题提到 "return ... same structure" 并且 body 包含看起来像 嵌套 结构的内容,我认为提供一些答案很重要自动下降和复制嵌套结构的惯用解决方案:

my %hash = :a{:b{:c,:d}}

say my %new-hash = %hash».&{ (0.9 .. 1.1) .rand }
# {a => {b => {c => 1.0476391741359872, d => 0.963626602773474}}}

# Update leaf values of original `%hash` in-place:
%hash».&{ $_ = (0.9 .. 1.1) .rand }

# Same effect:
%hash »*=» (0.9..1.1).rand;

# Same effect:
%hash.deepmap: { $_ = (0.9..1.1).rand }

Hyperops(例如»)迭代一个或两个数据结构以到达它们的叶子,然后应用被超化的运算:

say %hash».++ # in-place increment leaf values of `%hash` even if nested

.&{ ... } 使用方法调用语法在大括号中调用闭包。将它与 hyperop 结合起来可以写成:

%hash».&{ $_ = (0.9 .. 1.1) .rand }

另一种选择是.deepmap:

%hash.deepmap: { $_ = (0.9..1.1).rand }

hyperops 和 deepmap 之间的一个主要区别是允许编译器以任何顺序并行迭代数据结构和 运行 超操作,而 deepmap 迭代总是按顺序发生。