Digamos que tengo una matriz, y sé que voy a estar haciendo mucho "¿La matriz contiene X?" cheques. La forma eficiente de hacer esto es convertir esa matriz en un hash, donde las claves son los elementos de la matriz, y luego puede simplemente decir
si ($ hash {X}) {...}
¿Existe 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.
@hash{@keys} = undef;
La sintaxis aquí donde se refiere al hash con un
@
es un segmento 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 valor l, y lo estamos asignando a esa lista, que en realidad entra en el hash y establece los valores para todas las claves nombradas. En este caso, solo especifiqué un valor, por lo que ese valor entra$hash{$keys[0]}
, y las otras entradas hash se auto-vivifican (cobran vida) con valores indefinidos. [Mi sugerencia original aquí fue establecer la expresión = 1, que habría establecido esa clave en 1 y las otras enundef
. Lo cambié por coherencia, pero como veremos a continuación, los valores exactos no importan.]Cuando se dé cuenta de que lvalue, la expresión en el lado izquierdo de =, es una lista construida a partir del hash, entonces comenzará a tener algún sentido por qué estamos usando eso
@
. [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 claves. 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 simplemente ejecutar una
exists
verificación que preocuparse por el valor en el hash, aunque para mí lo importante aquí es solo el concepto de que estás representando un conjunto solo con las claves del hash. Además, alguien señaló que al usarundef
como valor aquí, consumiremos menos espacio de almacenamiento que si asignamos un valor. (Y también generar 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 otrosundef
, y algunas otras soluciones están dando vueltas para construir una matriz de valores para entrar el hachís; esfuerzo completamente inútil).fuente
= ()
, no= undef
, solo por coherencia al usar implícitamente undef para todos los valores, no solo para todos después del primero. (Como se ha demostrado en estos comentarios, es muy fácil ver elundef
y pensar que sólo se puede cambiar a 1 y afectan a todos los valores de hash.)Tenga en cuenta que si escribir
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 puede usar la breve y dulcefuente
Siempre pensé que
foreach my $item (@array) { $hash{$item} = 1 }
era al menos agradable y legible / mantenible.
fuente
Hay una presuposición aquí, que la forma más eficiente de hacer mucho "¿La matriz contiene X?" cheques es convertir la matriz en un hash. La eficiencia depende de los recursos escasos, a menudo de tiempo pero a veces de espacio y, a veces, del esfuerzo del programador. Al menos está duplicando la memoria consumida al mantener una lista y un hash de la lista simultáneamente. Además, está escribiendo más código original que necesitará probar, documentar, etc.
Como alternativa, mirada en el módulo de lista :: MoreUtils, específicamente las funciones
any()
,none()
,true()
yfalse()
. Todos toman un bloque como condicional y una lista como argumento, similar amap()
ygrep()
:print "At least one value undefined" if any { !defined($_) } @list;
Ejecuté una prueba rápida, cargando la mitad de / usr / share / dict / words en una matriz (25000 palabras), luego busqué once palabras seleccionadas de todo el diccionario (cada 5000 palabras) en la matriz, usando tanto la matriz -to-hash y la
any()
función de List :: MoreUtils.En Perl 5.8.8 construido a partir de la fuente, el método de matriz a hash se ejecuta casi 1100 veces más rápido que el
any()
método (1300 veces más rápido en el paquete Perl 5.8.7 de Ubuntu 6.06).Sin embargo, esa no es la historia completa: la conversión de matriz a hash tarda aproximadamente 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
any()
método. Sigue siendo bueno, pero no tan estelar.Mi intuición es que el método de matriz a hash va a superar
any()
en la mayoría de los casos, pero me sentiría mucho mejor si tuviera algunas métricas más sólidas (muchos casos de prueba, análisis estadísticos decentes, tal vez algunos grandes- O análisis algorítmico de cada método, etc.) Dependiendo de sus necesidades, List :: MoreUtils puede ser una mejor solución; ciertamente es más flexible y requiere menos codificación. Recuerde, la optimización prematura es un pecado ... :)fuente
List::MoreUtils
puede ser o no un método apropiado, dependiendo del caso de uso. Su caso de uso puede tener muchas búsquedas; otros pueden no hacerlo. El punto es que tanto la conversión de matriz a hash como laList::MoreUtils
resolución del problema subyacente de determinar la pertenencia; conocer varios enfoques le permite elegir el mejor método para su caso de uso específico.En perl 5.10, existe el operador ~~ cercano a la magia:
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
fuente
También vale la pena señalar por su integridad, mi método habitual para hacer esto con 2 matrices de la misma longitud
@keys
y@vals
que preferiría fuera un hash ...my %hash = map { $keys[$_] => $vals[$_] } (0..@keys-1);
fuente
@keys-1
es$#keys
.La solución de Raldi se puede ajustar a esto (el '=>' del original no es necesario):
my %hash = map { $_,1 } @array;
Esta técnica también se puede utilizar 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);
[EDITAR 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;
fuente
También puede usar Perl6 :: Junction .
use Perl6::Junction qw'any'; my @arr = ( 1, 2, 3 ); if( any(@arr) == 1 ){ ... }
fuente
Si realiza muchas operaciones teóricas de conjuntos, también puede usar Set :: Scalar o un módulo similar. A continuación,
$s = Set::Scalar->new( @array )
va a construir el Conjunto para usted - y se puede consultar con:$s->contains($m)
.fuente
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;
fuente
%hash = map{ $_, undef } @keylist
#!/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 (tenga en cuenta que las teclas repetidas obtienen el valor en la posición más grande en la matriz, es decir, 8-> 2 y no 6)
$VAR1 = { '8' => '2', '4' => '3', '9' => '1', '2' => '5', '5' => '4' };
fuente
También es posible que desee consultar Tie :: IxHash , que implementa matrices asociativas ordenadas. Eso le permitiría realizar ambos tipos de búsquedas (hash e índice) en una copia de sus datos.
fuente