2008-09-18 11 views
71

Digamos que tengo una matriz, y sé que voy a estar haciendo mucho "¿La matriz contiene X?" cheques. La forma más eficiente de hacer esto es convertir esa matriz en un hash, donde las teclas son los elementos de la matriz, y luego puede decir En Perl, ¿cómo creo un hash cuyas claves provienen de una matriz determinada?

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

¿Hay una manera fácil de hacer esta conversión de matriz a matriz? Idealmente, debería ser lo suficientemente versátil como para tomar una matriz anónima y devolver un hash anónimo.

Respuesta

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

No es tan corto como el "@hash {@array} = ..." soluciones, pero aquellos requieren el hash y la matriz que estar ya definido en otro lugar, mientras que éste puede tomar una serie anónima y devuelve un hash anónimo.

Lo que hace esto es tomar cada elemento en la matriz y emparejarlo con un "1". Cuando esta lista de pares (clave, 1, clave, 1, clave 1) se asigna a un hash, los impares se convierten en las claves del hash, y los pares se convierten en los respectivos valores.

34
@hash{@keys} = undef; 

La sintaxis aquí donde se está refiriendo al hash con un @ es una rebanada de hash. Básicamente estamos diciendo $hash{$keys[0]} Y $hash{$keys[1]} Y $hash{$keys[2]} ... es una lista en el lado izquierdo de =, un lvalue, y estamos asignando a esa lista, que en realidad entra en el hash y establece los valores para todos los llaves nombradas En este caso, solo especifiqué un valor, por lo que el valor entra en $hash{$keys[0]}, y las otras entradas hash todas se auto-vivifican (cobran vida) con valores indefinidos. [Mi sugerencia original aquí fue establecer la expresión = 1, que hubiera establecido esa clave en 1 y las demás en undef. Lo cambié por coherencia, pero como veremos a continuación, los valores exactos no importan.]

Cuando se da cuenta de que lvalue, la expresión en el lado izquierdo de =, es una lista construida de el hash, entonces comenzará a tener sentido por qué estamos usando ese @. [Excepto que creo que esto cambiará en Perl 6.]

La idea aquí es que usted está usando el hash como un conjunto. Lo que importa no es el valor que estoy asignando; es solo la existencia de las llaves. Así que lo que quiere hacer no es algo como:

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

lugar:

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

En realidad es más eficiente que ejecute un cheque exists que molestarse con el valor en el hash, aunque a mí lo importante aquí es solo el concepto de que estás representando un conjunto solo con las teclas del hash. Además, alguien señaló que al usar undef como valor aquí, consumiremos menos espacio de almacenamiento de lo que asignaríamos un valor. (Y también generan menos confusión, ya que el valor no importa, y mi solución asignaría un valor solo al primer elemento en el hash y dejaría los otros undef, y algunas otras soluciones están girando volteretas para construir una matriz de valores para ir en el hash, esfuerzo completamente desperdiciado).

+1

Éste es preferible a la otra porque no hace una lista temporal para inicializar el hash. Esto debería ser más rápido y consumir menos memoria. –

+0

Esto no funciona cuando se prueba: test.pl: mi @arr = ("foo", "barra", "Baz"); my @hash {@arr} = 1; error de sintaxis en la línea test.pl 2, cerca de "@hash {" – Frosty

+1

Frosty: Usted tiene que declarar "mi% de hash" en primer lugar, y luego hacer "@hash {@arr} = 1" (sin "mi"). –

2

solución de Raldi se puede apretar hasta este (el '=>' desde el original no es necesario):

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

Esta técnica también se puede utilizar para convertir las listas de texto en hashes:

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

Además, si usted tiene una línea de valores de la siguiente manera: "foo = 1, bar = 2, baz = 3" se puede hacer esto:

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

[EDITAR para incluir]


Otra solución que se ofrece (que tiene dos líneas) es:

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; 
+1

Lo diferente entre $ _ => 1 y $ _, 1 es puramente estilístico. Personalmente prefiero => ya que parece indicar el enlace clave/valor más explícitamente. Su solución @hash {@array} = 1 no funciona. Solo uno de los valores (el asociado con la primera clave en @array) se establece en 1. –

+0

Gracias por la aclaración. He editado la respuesta. – Frosty

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

Es una rebanada de hash, una lista de valores de hash, por lo que recibe la lista-y @ en el frente.

De the docs:

Si usted está confundido acerca de por qué se utiliza una '@' no sobre una rebanada de hash en lugar de un '%', piense en ello como esto. El tipo de soporte (cuadrado o rizado) gobierna ya sea una matriz o un hash siendo estudiada. En la otra mano , el símbolo inicial ('$' o '@') en la matriz o hash indica si está obteniendo un valor singular (un escalar) o uno plural (una lista).

+1

Guau, nunca había escuchado (o pensado) de eso. ¡Gracias! Tengo problemas para entender cómo funciona. ¿Puedes agregar una explicación? En particular, ¿cómo se puede tomar un hash llamado% hash y referirse a él con un signo @? – raldi

+1

raldi: es una porción hash, una lista de valores del hash, por lo que tiene la lista-y @ al frente. Ver http://perldoc.perl.org/perldata.html#Slices - particularmente el último párrafo de la sección – ysth

+0

¡Debe agregar eso a su respuesta! – raldi

2

También podría usar Perl6::Junction.

use Perl6::Junction qw'any'; 

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

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

Si lo haces varias veces para una matriz grande, eso será potencialmente mucho más lento. – ysth

+0

En realidad hacerlo una vez es mucho más lento. tiene que crear un objeto. Luego, poco después, destruirá ese objeto. Esto es solo un ejemplo de lo que es posible. –

5

en Perl 5.10, está el-cerca-magia ~~ operador:

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

Ver aquí: http://dev.perl.org/perl5/news/2007/perl-5.10.0.html

+0

Si lo hace varias veces para una matriz grande, es posible que sea mucho más lento. – ysth

+1

Sería el "operador de coincidencia inteligente" :) –

14

Tenga en cuenta que si escribir if (exists $hash{ key }) no es demasiado trabajo para usted (que prefiero utilizar ya que el tema de interés es realmente la presencia de una clave en lugar de la veracidad de su valor), entonces puede utilizar el corto y dulce

@hash{@key} =(); 
7

Hay una presuposición aquí, que la forma más eficiente de hacer un montón de "no contienen la matriz X?" checks es convertir la matriz a hash. La eficiencia depende del recurso escaso, a menudo el tiempo, pero a veces el espacio y, a veces el esfuerzo del programador. Al menos dobla la memoria consumida manteniendo una lista y un hash de la lista simultáneamente. Además, usted está escribiendo más código original que tendrá que probar, documento, etc.

Como alternativa, consulte el módulo Lista :: MoreUtils, específicamente las funciones any(), none(), true() y false().Todos ellos toman un bloque como el condicional y una lista como argumento, similar a map() y grep():

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

me hizo una prueba rápida, carga en medio de/usr/share/dict/words a una array (25000 palabras), luego busca once palabras seleccionadas de todo el diccionario (cada 5.000 palabras) en la matriz, utilizando el método de matriz a hash y la función any() de List :: MoreUtils.

En Perl 5.8.8 construido a partir de la fuente, el método de la matriz de hachís a 1100X va casi más rápido que el método any() (1300X más rápido bajo Ubuntu 6.06 de Perl envasados ​​5.8.7.)

Ese no es el Sin embargo, la conversión de array a hash demora alrededor de 0.04 segundos, lo que en este caso mata la eficiencia de tiempo del método array-to-hash a 1.5x-2x más rápido que el método any(). Sigue siendo bueno, pero no tan estelar.

Mi intuición es que el método array-to-hash va a vencer a any() en la mayoría de los casos, pero me sentiría mucho mejor si tuviera algunas medidas más sólidas (muchos casos de prueba, análisis estadísticos decentes , tal vez algún análisis algorítmico de gran O de cada método, etc.) Dependiendo de sus necesidades, List :: MoreUtils puede ser una solución mejor; sin duda es más flexible y requiere menos codificación. Recuerde, la optimización prematura es un pecado ... :)

+0

Lista :: MoreUtils es un gran consejo, gracias. – SquareCog

0

Es posible que también desee comprobar Tie::IxHash, que implementa ordenadas matrices asociativas. Eso le permitiría hacer ambos tipos de búsquedas (hash e índice) en una copia de sus datos.

1

Si realiza muchas operaciones teóricas establecidas, también puede usar Set::Scalar o un módulo similar. Luego, $s = Set::Scalar->new(@array) creará el conjunto para usted, y puede consultarlo con: $s->contains($m).

6

siempre pensé que

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

era al menos agradable y de fácil lectura/mantenible.

1

Puede colocar el código en una subrutina, si no quiere contaminar su espacio de nombres.

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

O aún mejor:

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; 

Si realmente quería pasar una referencia a un array:

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

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

'% hash = map {$ _, undef} @ keylist' –

0
#!/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); 

da (Nota claves repetidas obtener el valor en la mayor posición en la matriz - es decir, 8-> 2 y no 6)

$VAR1 = { 
      '8' => '2', 
      '4' => '3', 
      '9' => '1', 
      '2' => '5', 
      '5' => '4' 
     }; 
+0

Un hasref parece más que un poco exagerado aquí. – bobbogo

2

También digno de mención para la integridad, mi método habitual de hacer esto con 2 conjuntos de la misma longitud y @keys@vals que preferirían eran un hash ...

my %hash = map { $keys[$_] => $vals[$_] } ([email protected]);

+4

La expresión usual para '@ keys-1' es' $ # keys'. –

+0

@StefanMajewsky No he visto ese realmente usado en mucho tiempo. Me mantengo alejado de él, es feo. –

Cuestiones relacionadas