arrays - En Perl, ¿cómo creo un hash cuyas claves provienen de una matriz determinada?
(14)
Aquí hay una presuposición, que la forma más eficiente de hacer mucho "¿El conjunto contiene 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, estás escribiendo un código más original que necesitarás probar, documentar, etc.
Como alternativa, mire el módulo List :: MoreUtils, específicamente las funciones any()
, none()
, true()
y false()
. Todos toman un bloque como condicional y una lista como argumento, similar a map()
y grep()
:
print "At least one value undefined" if any { !defined($_) } @list;
Ejecuté una prueba rápida, cargando en la mitad de / usr / share / dict / words a una matriz (25000 palabras), luego buscando once palabras seleccionadas de todo el diccionario (cada 5000th palabra) en la matriz, usando tanto la matriz -to-hash método y la función any()
de List :: MoreUtils.
En Perl 5.8.8 construido a partir de la fuente, el método array-to-hash se ejecuta casi 1100 veces más rápido que el método any()
(1300 veces más rápido en el paquete Perl 5.8.7 de Ubuntu 6.06).
Sin embargo, esa no es toda la historia: 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 de matriz a 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 superar any()
en la mayoría de los casos, pero me sentiría mucho mejor si tuviera algunas mediciones más sólidas (muchos casos de prueba, análisis estadísticos decentes, tal vez algunos análisis algorítmicos 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 ... :)
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 puedes simplemente decir
if($hash{X}) { ... }
¿Hay una manera fácil de hacer esta conversión de matriz a hash? Idealmente, debería ser lo suficientemente versátil como para tomar una matriz anónima y devolver un hash anónimo.
En Perl 5.10, está el operador ~ ~ cercano a la magia:
sub invite_in {
my $vampires = [ qw(Angel Darla Spike Drusilla) ];
return ($_[0] ~~ $vampires) ? 0 : 1 ;
}
Vea aquí: http://dev.perl.org/perl5/news/2007/perl-5.10.0.html
La solución de Raldi se puede ajustar hasta esto (el ''=>'' del original no es necesario):
my %hash = map { $_,1 } @array;
Esta técnica también se puede usar para convertir listas de texto en hashes:
my %hash = map { $_,1 } split(",",$line)
Además, si tiene una línea de valores como esta: "foo = 1, bar = 2, baz = 3" puede hacer esto:
my %hash = map { split("=",$_) } split(",",$line);
[EDIT para incluir]
Otra solución ofrecida (que toma 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;
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 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 desea pasar una referencia 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;
Si realiza muchas operaciones teóricas establecidas, también puede usar Set::Scalar o un módulo similar. Entonces $s = Set::Scalar->new( @array )
creará el conjunto para usted, y puede consultarlo con: $s->contains($m)
.
Siempre pensé que
foreach my $item (@array) { $hash{$item} = 1 }
fue al menos agradable y legible / mantenible.
También Perl6::Junction usar Perl6::Junction .
use Perl6::Junction qw''any'';
my @arr = ( 1, 2, 3 );
if( any(@arr) == 1 ){ ... }
También es posible que desees consultar Tie::IxHash , que implementa matrices asociativas ordenadas. Eso le permitiría hacer ambos tipos de búsquedas (hash e índice) en una copia de sus datos.
También vale la pena señalar para la integridad, mi método habitual para hacer esto con 2 arrays de la misma longitud @keys
y @vals
que preferiría que fueran un hash ...
my %hash = map { $keys[$_] => $vals[$_] } (0..@keys-1);
Tenga en cuenta que si escribe if ( exists $hash{ key } )
no es demasiado trabajo para usted (que prefiero usar ya que el tema de interés es realmente la presencia de una clave en lugar de la veracidad de su valor), entonces usted puede usar el corto y dulce
@hash{@key} = ();
@hash{@array} = (1) x @array;
Es una porción hash, una lista de valores del hash, por lo que tiene la lista-y @ al frente.
De los documentos :
Si está confundido acerca de por qué usa una ''@'' en una porción hash en lugar de un ''%'', piense de esta manera. El tipo de corchete (cuadrado o rizado) rige si se trata de una matriz o un hash que se mira. Por otro lado, el símbolo inicial (''$'' o ''@'') en la matriz o hash indica si está recuperando un valor singular (un escalar) o uno plural (una lista).
#!/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 (observe que las teclas repetidas obtienen el valor en la posición más alta de la matriz, es decir, 8-> 2 y no 6)
$VAR1 = {
''8'' => ''2'',
''4'' => ''3'',
''9'' => ''1'',
''2'' => ''5'',
''5'' => ''4''
};
%hash = map { $_ => 1 } @array;
No es tan corta como las soluciones "@hash {@array} = ...", pero esas requieren que el hash y la matriz ya estén definidas en otro lugar, mientras que esta puede tomar una matriz anónima y devolver un hash anónimo.
Lo que hace es tomar cada elemento de 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.
@hash{@keys} = undef;
La sintaxis aquí donde te refieres al hash con un @
es un corte hash. Básicamente, estamos diciendo $hash{$keys[0]}
AND $hash{$keys[1]}
AND $hash{$keys[2]}
... es una lista en el lado izquierdo de =, lvalue, y estamos asignando a esa lista, que en realidad entra en el hash y establece los valores para todas las claves con nombre. En este caso, solo especifiqué un valor, por lo que el valor va a $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 te das cuenta de que el valor l, la expresión en el lado izquierdo de la =, es una lista construida a partir del 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 estás usando el hash como un conjunto. Lo que importa no es el valor que estoy asignando; es solo la existencia de las llaves. Entonces, lo que quieres hacer no es algo como:
if ($hash{$key} == 1) # then key is in the hash
en lugar:
if (exists $hash{$key}) # then key is in the set
En realidad, es más eficiente ejecutar un cheque exists
que preocuparse por el valor del hash, aunque para 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 a los demás sin undef
, y algunas otras soluciones están haciendo volteretas para construir una matriz de valores para ir en el hash, esfuerzo completamente desperdiciado).