Неожиданное поведение каждого - PullRequest
2 голосов
/ 12 июля 2011
%h = (a => 1, b => 2);

keys %h;
while(my($k, $v) = each %h)
{
  $h{uc $k} = $h{$k} * 2; # BAD IDEA!
}

Вывод:

(a => 1, A => 2, b => 2, B => 8)

вместо

(a => 1, A => 2, b => 2, B => 4)

Почему?

Ответы [ 5 ]

7 голосов
/ 12 июля 2011

С perldoc -f каждый

Если вы добавляете или удаляете элементы хеша во время итерации, записи могут быть пропущены или дублированы - так что не делайте этого,Исключение: всегда безопасно удалить элемент, последний раз возвращенный each().

1 голос
/ 12 июля 2011

Цикл изменяется %h на лету, поэтому он интерпретирует дважды значение b (сначала b, затем B). Семантика each работает, удаляя пару из хеша, а затем возвращая ее, но затем вы добавляете ее в цикл, чтобы она могла быть обработана позже. Сначала вы должны получить ключи, а затем зациклить их, чтобы получить значения. Например:

my @keys = keys %h;
foreach (@keys)
{
 $h{uc $_} = $h{$_} * 2;
 delete $h{$_};
}

Как час. Оуэнс указал выше, так как each удаляет элемент, вы должны удалить их тоже.

Еще одна милая вещь, которую вы можете сделать, это использовать карту для создания нового хэша:

my %result  = map {uc $_ => $h{$_} * 2} (keys %h);

, а затем используйте хэш %result.

1 голос
/ 12 июля 2011

Поскольку each не позволяет вам изменять элементы на месте, как это делает цикл for.each просто возвращает следующий ключ и значение для хэша.Вы создаете новые значения в хэше, когда говорите $h{uc $k} = $h{$k} * 2;.Чтобы получить желаемое поведение, я бы сказал:

for my $k (keys %h) {
    $h{uc $k} = $h{$k};
    delete $h{$k};
}

Если хеш огромен, и вы беспокоитесь о сохранении всех ключей в памяти (что является основным использованием each), тогдавам лучше сказать:

my %new_hash;
while (my ($k, $v) = each %h) {
    $new_hash{uc $k} = $v;
    delete $h{$k};
}

, а затем использовать %new_hash вместо %h.

Почему некоторые ключи обрабатываются более одного раза, а другие - нет, сначала мы должны обратиться к документации для each:

Если вы добавляете или удаляете элементы хеша во время итерации, записи могут быть пропущены или дублированы - такне делай этого.

Хорошо, он говорит нам, чего ожидать, но не почему.Чтобы понять, почему мы должны создать модель того, что происходит.Когда вы присваиваете значение хешу, ключ превращается в число с помощью хеш-функции .Этот номер затем используется для индексации в массив (на уровне C, а не на уровне Perl).Для наших целей мы можем использовать очень упрощенную модель:

#!/usr/bin/perl

use strict;
use warnings;

use Data::Dumper;

my %hash_function = (
        a => 2,
        b => 1,
        A => 0,
        B => 3
);

my @hash_table;

{
    my $position = 0;
    sub my_each {
        #return nothing if there is nothing
        return unless @hash_table;

        #get the key and value from the next positon in the
        #hash table, skipping empty positions
        until (defined $hash_table[$position]) {
            $position++;
            #return nothing if there is nothing left in the array
            return if $position > $#hash_table;
        }
        my ($k, $v) = %{$hash_table[$position]};

        #set up for the next call
        $position++;

        #if in list context, return both key an value
        #if in scalar context, return the key
        return wantarray ? ($k, $v) : $k;
    }
}


$hash_table[$hash_function{a}] = { a => 1 }; # $h{a} = 1;
$hash_table[$hash_function{b}] = { b => 2 }; # $h{b} = 2;

while (my ($k, $v) = my_each) {
    # $h{$k} = $v * 2;
    $hash_table[$hash_function{uc $k}] = { uc $k => $v * 2 };
}

print Dumper \@hash_table;

. В этом примере мы видим, что когда ключ "A" добавляется в хеш-таблицу, он помещается перед другими ключами.таким образом, он не обрабатывается во второй раз, но клавиша "B" делает размещенной после других клавиш, поэтому функция my_each видит ее при первом проходе (как элемент, следующий заключ "a").

0 голосов
/ 12 июля 2011

Добавление warn $k; в ваш цикл может сделать вещи немного более ясными - я получаю тот же результат, что и вы, и это потому, что ключи, которые он использует в конечном итоге, это «a», «b», а затем «B». ', так:

#round 1 ($k='a'):
$h{uc 'a'} = 1 * 2;
# $h{A} = 2;

#round 2: ($k='b'):
$h{uc 'b'} = 2 * 2;
# $h{B} = 4;

#round 3: ($k='B'):
$h{uc 'B'} = 4 * 2;
# $h{B} = 8;

Почему он запускает цикл с клавишей «B», а не «A»? Это связано с тем, что вызов each выполняется каждый раз, когда он проходит цикл (поэтому он работает с новой версией хэша), но он запоминает последнее значение, с которым работал, поэтому в этом случае, когда 'A' добавляется к хешу, ему назначается позиция перед 'a', поэтому его никогда не видят.

0 голосов
/ 12 июля 2011

Это работает для меня

%h = (a => 1, b => 2);
keys %h;
for my $k (keys %h ) {
    $h{uc $k} = $h{$k} * 2;
}
while ( ($k,$v) = each %h ) {
    print "$k => $v\n";
}

Выход:

A => 2
a => 1
b => 2
B => 4
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...