Pergunta

Se eu tiver um hash Perl com vários pares (chave, valor), qual é o método preferido de iterar todas as chaves?Eu ouvi isso usando each pode de alguma forma ter efeitos colaterais indesejados.Então, isso é verdade e um dos dois métodos a seguir é o melhor ou existe uma maneira melhor?

# Method 1
while (my ($key, $value) = each(%hash)) {
    # Something
}

# Method 2
foreach my $key (keys(%hash)) {
    # Something
}
Foi útil?

Solução

A regra é usar a função mais adequada às suas necessidades.

Se você só quer as chaves e não planeja nunca ler qualquer um dos valores, use chaves():

foreach my $key (keys %hash) { ... }

Se você quiser apenas os valores, use valores():

foreach my $val (values %hash) { ... }

Se você precisar das chaves e os valores, use each():

keys %hash; # reset the internal iterator so a prior each() doesn't affect the loop
while(my($k, $v) = each %hash) { ... }

Se você planeja alterar as chaves do hash de alguma forma exceto para excluir a chave atual durante a iteração, você não deve usar each().Por exemplo, este código para criar um novo conjunto de chaves maiúsculas com valores duplicados funciona bem usando chaves():

%h = (a => 1, b => 2);

foreach my $k (keys %h)
{
  $h{uc $k} = $h{$k} * 2;
}

produzindo o hash resultante esperado:

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

Mas usando each() para fazer a mesma coisa:

%h = (a => 1, b => 2);

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

produz resultados incorretos de maneiras difíceis de prever.Por exemplo:

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

Isso, no entanto, é seguro:

keys %h;
while(my($k, $v) = each %h)
{
  if(...)
  {
    delete $h{$k}; # This is safe
  }
}

Tudo isso está descrito na documentação perl:

% perldoc -f keys
% perldoc -f each

Outras dicas

Uma coisa que você deve estar ciente ao usar each é que ele tem o efeito colateral de adicionar "estado" ao seu hash (o hash precisa lembrar qual é a tecla "Next").Ao usar código como os trechos publicados acima, que iteram em todo o hash de uma só vez, isso geralmente não é um problema.No entanto, você terá problemas difíceis de rastrear (eu falo da experiência;), ao usar each juntamente com declarações comolast ou return para sair do while ... each Faça um loop antes de você ter processado todas as chaves.

Nesse caso, o hash lembrará quais chaves ele já retornou e quando você usa each Nele na próxima vez (talvez em uma peça de código não relacionada), ele continuará nesta posição.

Exemplo:

my %hash = ( foo => 1, bar => 2, baz => 3, quux => 4 );

# find key 'baz'
while ( my ($k, $v) = each %hash ) {
    print "found key $k\n";
    last if $k eq 'baz'; # found it!
}

# later ...

print "the hash contains:\n";

# iterate over all keys:
while ( my ($k, $v) = each %hash ) {
    print "$k => $v\n";
}

Isso imprime:

found key bar
found key baz
the hash contains:
quux => 4
foo => 1

O que aconteceu com as teclas "bar" e baz"?Eles ainda estão lá, mas o segundo each começa onde o primeiro parou e para quando chega ao final do hash, então nunca os vemos no segundo loop.

O lugar é aqui each pode causar problemas é que ele é um iterador verdadeiro e sem escopo.A título de exemplo:

while ( my ($key,$val) = each %a_hash ) {
    print "$key => $val\n";
    last if $val; #exits loop when $val is true
}

# but "each" hasn't reset!!
while ( my ($key,$val) = each %a_hash ) {
    # continues where the last loop left off
    print "$key => $val\n";
}

Se você precisa ter certeza de que each obtém todas as chaves e valores, você precisa ter certeza de usar keys ou values primeiro (pois isso redefine o iterador).Veja o documentação para cada.

Usar a sintaxe each impedirá que todo o conjunto de chaves seja gerado de uma só vez.Isso pode ser importante se você estiver usando um hash vinculado a um banco de dados com milhões de linhas.Você não deseja gerar a lista inteira de chaves de uma só vez e esgotar sua memória física.Nesse caso, cada um serve como um iterador, enquanto as chaves, na verdade, geram o array inteiro antes do início do loop.

Portanto, o único lugar onde "cada" é realmente útil é quando o hash é muito grande (em comparação com a memória disponível).É provável que isso aconteça apenas quando o hash em si não reside na memória, a menos que você esteja programando um dispositivo portátil de coleta de dados ou algo com pouca memória.

Se a memória não for um problema, geralmente o paradigma do mapa ou das chaves é o paradigma mais predominante e mais fácil de ler.

Algumas reflexões diversas sobre este tópico:

  1. Não há nada de inseguro em nenhum dos próprios iteradores de hash.O que não é seguro é modificar as chaves de um hash enquanto você o itera.(É perfeitamente seguro modificar os valores.) O único efeito colateral potencial em que consigo pensar é que values retorna aliases, o que significa que modificá-los modificará o conteúdo do hash.Isso ocorre intencionalmente, mas pode não ser o que você deseja em algumas circunstâncias.
  2. João resposta aceita é bom com uma exceção:a documentação deixa claro que não é seguro adicionar chaves durante a iteração em um hash.Pode funcionar para alguns conjuntos de dados, mas falhará para outros, dependendo da ordem do hash.
  3. Como já foi observado, é seguro excluir a última chave retornada por each.Isso é não verdadeiro para keys como each é um iterador enquanto keys retorna uma lista.

Eu sempre uso o método 2 também.O único benefício de usar cada um é que, se você estiver apenas lendo (em vez de reatribuir) o valor da entrada de hash, não estará constantemente desreferenciando o hash.

Posso ser mordido por este, mas acho que é uma preferência pessoal.Não consigo encontrar nenhuma referência nos documentos para que cada() seja diferente de chaves() ou valores() (além da resposta óbvia "eles retornam coisas diferentes").Na verdade, os documentos afirmam usar o mesmo iterador e todos eles retornam valores de lista reais em vez de cópias deles, e que modificar o hash enquanto itera sobre ele usando qualquer chamada é ruim.

Dito isso, quase sempre uso keys() porque para mim geralmente é mais autodocumentado acessar o valor da chave por meio do próprio hash.Ocasionalmente, uso valores() quando o valor é uma referência a uma estrutura grande e a chave do hash já estava armazenada na estrutura, ponto em que a chave é redundante e não preciso dela.Acho que usei each() 2 vezes em 10 anos de programação Perl e provavelmente foi a escolha errada nas duas vezes =)

Eu costumo usar keys e não consigo pensar na última vez que usei ou li um uso de each.

Não se esqueça map, dependendo do que você está fazendo no loop!

map { print "$_ => $hash{$_}\n" } keys %hash;

Eu diria:

  1. Use o que for mais fácil de ler/entender para a maioria das pessoas (normalmente, eu diria que são chaves)
  2. Use o que você decidir de forma consistente em toda a base de código.

Isso oferece 2 vantagens principais:

  1. É mais fácil identificar código "comum" para que você possa refatorar funções/métodos.
  2. É mais fácil para futuros desenvolvedores manterem.

Não acho que seja mais caro usar chaves em cada uma, portanto não há necessidade de duas construções diferentes para a mesma coisa no seu código.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top