Умножьте все значения в хеше% и верните хеш% с той же структурой.

У меня есть JSON, хранящийся в столбце базы данных, который выглядит так:

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

Как часть алгоритма генерации, над которым я работаю, я хотел бы взять это значение в% хэш, умножить его на (0.9..1.1).rand (чтобы учесть «естественную 10% дисперсию по высоте»), а затем создать новый % хеша в той же структуре. Мой 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 процедуру, чтобы получить «средние» высоты (как в метрических, так и в английских единицах) для этого вида.

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

Есть ли более простой (и рабочий) способ сделать это без дублирования кода для каждого элемента? :)


person kawaii    schedule 18.04.2019    source источник
comment
Используйте гиперопсии, например. my %hash = :a, :b; say %hash».rand # {a => 0.12201023810083333, b => 0.3360358162308337}   -  person raiph    schedule 18.04.2019
comment
Кроме того, если вы не обрабатываете %heights в методе get-height, вы можете просто удалить его вместе с оператором return, оставив только вызов метода для values-or-defaults.   -  person Takao    schedule 18.04.2019


Ответы (2)


Во-первых, проблема с логикой вашего кода. Первоначально вы получаете хэш значений, "feet": "6'07\"", "meters": 2.0 проанализированный из json, где meters - число, а feet - строка. Затем вы пытаетесь умножить его на случайное значение ... И хотя это будет работать для числа, это не будет работать для строки. На самом деле алломорфы Perl 6 позволяют вам это делать: say "5" * 3 вернет 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 }).

Конечно, можно сыграть в гольф или написать по-другому, но так решается главный вопрос.

person Takao    schedule 18.04.2019
comment
Или измените значения на месте с помощью метаоператора: %a >>*=>> (0.9 .. 1.1).rand - person Elizabeth Mattijsen; 18.04.2019

Вы приняли ответ @Takao. Это решение требует вручную покопаться в %hash, чтобы получить хэши / списки листьев, а затем применить map.

Учитывая, что в заголовке вашего вопроса упоминается "return ... same structure", а в теле есть то, что выглядит как вложенный структура, я считаю важным, что есть ответ, предлагающий некоторые идиоматические решения для автоматического спуска и дублирования вложенной структуры:

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

.&{ ... } вызывает закрытие в фигурных скобках, используя синтаксис вызова метода. Комбинируя это с гиперметропой, можно написать:

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

Другой вариант - .deepmap:

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

Ключевое различие между гиперопсами и deepmap заключается в том, что компилятору разрешено перебирать структуры данных и запускать гипероперации параллельно в любом порядке, тогда как итерация deepmap всегда происходит последовательно.

person raiph    schedule 18.04.2019