Em Perl, como posso criar um hash cujas chaves vêm de uma determinada matriz?

StackOverflow https://stackoverflow.com/questions/95820

  •  01-07-2019
  •  | 
  •  

Pergunta

Digamos que eu tenho uma matriz, e eu sei que eu vou estar fazendo um monte de "Será que a matriz conter X?" Verificações. A maneira eficiente de fazer isso é transformar essa matriz em um hash, onde as chaves são elementos do array, e então você pode apenas dizer

if($hash{X}) { ... }

Existe uma maneira fácil de fazer esta conversão matriz de hash? Idealmente, deve ser versátil o suficiente para tomar uma matriz anônima e retornar um hash anônimo.

Foi útil?

Solução

%hash = map { $_ => 1 } @array;

Não é tão curto como o "@hash {@array} = ..." soluções, mas aqueles exigem o hash e array para já ser definido em outro lugar, ao passo que este pode tomar uma matriz anônima e retornar um anônimo hash.

O que isto faz é tomar cada elemento na matriz e emparelhá-lo com um "1". Quando esta lista de (key, 1, chave, 1,-chave 1) pares são designados para um hash, os ímpares tornar as chaves do hash e os pares tornaram-se os respectivos valores.

Outras dicas

 @hash{@array} = (1) x @array;

É uma fatia de hash, uma lista de valores de hash, assim que começa a @ lista-y na frente.

A partir os docs :

Se você está confuso sobre por que você usa um '@' lá em uma fatia de hash em vez de um '%', pense nisso como este. o tipo de suporte (quadrado ou encaracolado) governa se é uma matriz ou uma de hash ser analisado. No outro lado, o símbolo líder ( '$' ou '@') sobre a matriz ou de hash indica se você está recebendo de volta um valor singular (A escalar) ou um plural (uma lista).

@hash{@keys} = undef;

A sintaxe aqui onde você está se referindo ao hash com um @ é uma fatia de hash. Estamos dizendo basicamente $hash{$keys[0]} E $hash{$keys[1]} E $hash{$keys[2]} ... é uma lista no lado esquerdo da =, um lvalue, e estamos atribuindo a essa lista, o que realmente vai para o hash e define os valores para toda a chaves nomeados. Neste caso, eu só especificado um valor, de modo que o valor vai para $hash{$keys[0]}, e as outras entradas de hash tudo auto-Vivify (vir à vida) com valores indefinidos. [Minha sugestão original aqui foi definir a expressão = 1, que teria definido que uma chave para 1 e os outros para undef. Eu mudei para a consistência, mas, como veremos abaixo, os valores exatos não importam.]

Quando você percebe que o lvalue, a expressão no lado esquerdo da =, é uma lista construída a partir do hash, então ele vai começar a fazer algum sentido por isso que estamos usando esse @. [Só que eu acho que isso vai mudar em Perl 6.]

A idéia aqui é que você está usando o hash como um conjunto. O que importa não é o valor que eu estou atribuindo; é apenas a existência das chaves. Então, o que você quer fazer não é algo como:

if ($hash{$key} == 1) # then key is in the hash

em vez disso:

if (exists $hash{$key}) # then key is in the set

Na verdade, é mais eficiente para apenas executar uma verificação exists do que se preocupar com o valor no hash, embora para mim o importante aqui é apenas o conceito de que você está representando um conjunto apenas com as chaves do hash. Além disso, alguém apontou que usando undef como o valor aqui, vamos consumir menos espaço de armazenamento do que seria atribuir um valor. (E também geram menos confusão, como o valor não importa, e minha solução seria atribuir um valor apenas para o primeiro elemento no hash e deixar a outros undef, e algumas outras soluções estão virando cambalhotas para construir uma matriz de valores para ir para o hash; esforço completamente desperdiçado)

.

Note que, se digitação if ( exists $hash{ key } ) não é muito trabalho para você (que eu prefiro usar uma vez que a questão de interesse é realmente a presença de uma chave, em vez do truthiness do seu valor), então você pode usar a curto e doce

@hash{@key} = ();

Há um pressuposto aqui, que a maneira mais eficiente de fazer um monte de "Será que a matriz conter X?" controlos é converter a matriz para um hash. Eficiência depende do recurso escasso, muitas vezes o tempo, mas às vezes o espaço e esforço, por vezes programador. Está, pelo menos, duplicar a memória consumida por manter uma lista e um hash da lista torno simultaneamente. Além disso, você está escrevendo código mais original que você precisa de teste, documentos, etc.

Como alternativa, olhar para a lista :: moreutils módulo, especificamente as funções any(), none(), true() e false(). todas elas têm um bloco como a condicional e uma lista como argumento, semelhante ao map() e grep():

print "At least one value undefined" if any { !defined($_) } @list;

Eu corri um teste rápido, o carregamento em metade dos / usr / share / dict / palavras para uma matriz (25000 palavras), em seguida, à procura de onze palavras selecionadas de todo o dicionário inteiro (cada palavra 5000) na matriz, usando tanto o método de array-to-de hash ea função any() da Lista :: moreutils.

Perl 5.8.8 construído a partir da fonte, do método de matriz-de-hash de corre quase 1100x mais rápido do que o método any() (1300x mais rápido sob Ubuntu 6.06 embalados de Perl 5.8.7.)

Essa não é a história completa no entanto - a conversão de matriz-to-de hash leva cerca de 0,04 segundos, o que, neste caso, mata a eficiência de tempo de método de array-to-hash para 1,5x-2x mais rápido do que o método any(). Ainda bem, mas não tão estelar.

A minha intuição é que o método de array-to-hash é indo para vencer any() na maioria dos casos, mas eu me sentiria muito melhor se eu tivesse algumas métricas mais sólidas (lotes de casos de teste, as análises estatísticas decentes, talvez alguns grandes O-análise algorítmica de cada método, etc.) Dependendo de suas necessidades, List :: moreutils pode ser uma solução melhor; é certamente mais flexível e requer menos codificação. Lembre-se, otimização prematura é um pecado ...:)

Eu sempre pensei que

foreach my $item (@array) { $hash{$item} = 1 }

era pelo menos agradável e legível / sustentável.

Em perl 5.10, há o operador de fim-de-magic ~~:

sub invite_in {
    my $vampires = [ qw(Angel Darla Spike Drusilla) ];
    return ($_[0] ~~ $vampires) ? 0 : 1 ;
}

Veja aqui: http: //dev.perl. org / perl5 / news / 2007 / perl-5.10.0.html

Também digno de nota para a integralidade, o meu método usual para fazer isso com 2 matrizes do mesmo comprimento @keys e @vals que você preferir eram um hash ...

my %hash = map { $keys[$_] => $vals[$_] } (0..@keys-1);

A solução da Raldi pode ser apertado até este (a '=>' a partir do original não é necessário):

my %hash = map { $_,1 } @array;

Esta técnica pode também ser usada para transformar as listas de texto em hashes:

my %hash = map { $_,1 } split(",",$line)

Além disso, se você tem uma linha de valores como este: "foo = 1, bar = 2, baz = 3" você pode fazer isso:

my %hash = map { split("=",$_) } split(",",$line);

[EDIT para incluir]


Outra solução oferecida (que leva duas linhas) é:

my %hash;
#The values in %hash can only be accessed by doing exists($hash{$key})
#The assignment only works with '= undef;' and will not work properly with '= 1;'
#if you do '= 1;' only the hash key of $array[0] will be set to 1;
@hash{@array} = undef;

Você também pode usar Perl6 :: Junção .

use Perl6::Junction qw'any';

my @arr = ( 1, 2, 3 );

if( any(@arr) == 1 ){ ... }

Se você fizer um monte de conjunto de operações teóricas - você também pode usar Set :: escalar ou módulo similar. Então $s = Set::Scalar->new( @array ) vai construir o conjunto para você - e você pode consultá-lo com:. $s->contains($m)

Você pode colocar o código em uma sub-rotina, se você não quer poluir seu namespace.

my $hash_ref =
  sub{
    my %hash;
    @hash{ @{[ qw'one two three' ]} } = undef;
    return \%hash;
  }->();

Ou melhor ainda:

sub keylist(@){
  my %hash;
  @hash{@_} = undef;
  return \%hash;
}

my $hash_ref = keylist qw'one two three';

# or

my @key_list = qw'one two three';
my $hash_ref = keylist @key_list;

Se você realmente queria passar uma referência de matriz:

sub keylist(\@){
  my %hash;
  @hash{ @{$_[0]} } = undef if @_;
  return \%hash;
}

my @key_list = qw'one two three';
my $hash_ref = keylist @key_list;

Você também pode querer verificar para fora Tie :: IxHash, que implementa ordenou arrays associativos. Isso permitiria que você faça os dois tipos de pesquisas (de hash e índice) em uma cópia de seus dados.

#!/usr/bin/perl -w

use strict;
use Data::Dumper;

my @a = qw(5 8 2 5 4 8 9);
my @b = qw(7 6 5 4 3 2 1);
my $h = {};

@{$h}{@a} = @b;

print Dumper($h);

dá (chaves de notas repetidas obter o valor em maior posição na matriz - ou seja 8-> 2 e não 6)

$VAR1 = {
          '8' => '2',
          '4' => '3',
          '9' => '1',
          '2' => '5',
          '5' => '4'
        };
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top