En Perl, ¿cómo creo un hash cuyas claves provienen de una matriz determinada?


80

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.

Respuestas:


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

No es tan corto como las soluciones "@hash {@array} = ...", pero aquellas requieren que el hash y la matriz ya estén definidos 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 valores respectivos.


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

Es un trozo de 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 '@' allí en un trozo de hash en lugar de un '%', piénselo así. El tipo de corchete (cuadrado o rizado) determina si se está observando una matriz o un hash. Por otro lado, el símbolo inicial ('$' o '@') en la matriz o hash indica si está obteniendo un valor singular (un escalar) o plural (una lista).


1
Vaya, nunca escuché (ni pensé en) ese. ¡Gracias! Me cuesta entender cómo funciona. ¿Puedes agregar una explicación? En particular, ¿cómo puede tomar un hash llamado% hash y referirse a él con un signo @?
Raldi

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

¡Deberías agregar eso a tu respuesta!
raldi

¿Podría explicarnos también el RHS? Gracias.
Susheel Javadi

1
(lista) x $ número replica la lista $ número veces. El uso de una matriz en contexto escalar devuelve el número de elementos, por lo que (1) x @array es una lista de unos con la misma longitud que @array.
moritz

39
@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 en undef. 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 existsverificació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 usar undefcomo 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 otros undef, y algunas otras soluciones están dando vueltas para construir una matriz de valores para entrar el hachís; esfuerzo completamente inútil).


1
Este es preferible al otro porque no hace una lista temporal para inicializar el hash. Esto debería ser más rápido y consumir menos memoria.
Leon Timmermans

1
Frosty: Primero debes declarar "my% hash", luego hacer "@hash {@arr} = 1" (no "my").
Michael Carman

8
= (), 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 el undefy pensar que sólo se puede cambiar a 1 y afectan a todos los valores de hash.)
ysth

2
Como los valores terminan como "indef" aquí (y probablemente no por la razón que usted cree, como ha señalado ysth), no puede usar el hash en un código como "if ($ hash {$ value})". Necesitaría "si (existe $ hash {$ valor})".
Dave Cross

2
Sería bueno si editara su respuesta para señalar que debe usarse con existe, que existe es más eficiente que verificar la veracidad cargando el valor hash, y que undef ocupa menos espacio que 1.
bhollis

16

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 dulce

@hash{@key} = ();

8

Siempre pensé que

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

era al menos agradable y legible / mantenible.


7

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()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 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 ... :)


Esto no responde a la pregunta. También pierde el punto ... la conversión de matriz a hash solo ocurre una vez ... un total de 0.04 segundos (en 2008) agregados al tiempo de ejecución del programa, mientras que las búsquedas ocurren muchas veces.
Jim Balter

2
Intenté resolver el problema subyacente, no solo responder la pregunta. List::MoreUtilspuede 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 la List::MoreUtilsresolució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.
ArcLight


5

También vale la pena señalar por su integridad, mi método habitual para hacer esto con 2 matrices de la misma longitud @keysy @valsque preferiría fuera un hash ...

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


4
El modismo habitual para @keys-1es $#keys.
Stefan Majewsky

@StefanMajewsky No he visto ese realmente usado en un tiempo. Yo mismo me mantengo alejado de él, es feo.
Tamzin Blake

3

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;

1
La diferencia entre $ _ => 1 y $ _, 1 es puramente estilística. Personalmente prefiero => ya que parece indicar el enlace clave / valor de manera más explícita. Tu solución @hash {@array} = 1 no funciona. Solo uno de los valores (el asociado con la primera clave en @array) se establece en 1.
Dave Cross

2

También puede usar Perl6 :: Junction .

use Perl6::Junction qw'any';

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

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

1
Si lo hace varias veces para una matriz grande, potencialmente será mucho más lento.
ysth

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

1

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).


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

%hash = map{ $_, undef } @keylist
Brad Gilbert

1
#!/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'
        };

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

0

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.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.