2008-10-01 20 views
5

Si tiene un hash (o referencia a un hash) en perl con muchas dimensiones y desea recorrer todos los valores, ¿cuál es la mejor manera de hacerlo? En otras palabras, si tenemos $ f -> {$ x} {$ y}, quiero algo así comoAtravesar un hash multidimensional en Perl

foreach ($x, $y) (deep_keys %{$f}) 
{ 
} 

en lugar de

foreach $x (keys %f) 
    { 
    foreach $y (keys %{$f->{$x}) 
    { 
    } 
} 
+0

Un contexto de lista no va a devolver una lista segmentada, sino una lista aplanada, por lo que no obtendrá dos escalares al mismo tiempo. Es mejor que deep_keys devuelva una referencia de matriz. – Axeman

Respuesta

11

Aquí hay una opción. Esto funciona para arbitrariamente profundas hashes:

sub deep_keys_foreach 
{ 
    my ($hashref, $code, $args) = @_; 

    while (my ($k, $v) = each(%$hashref)) { 
     my @newargs = defined($args) ? @$args :(); 
     push(@newargs, $k); 
     if (ref($v) eq 'HASH') { 
      deep_keys_foreach($v, $code, \@newargs); 
     } 
     else { 
      $code->(@newargs); 
     } 
    } 
} 

deep_keys_foreach($f, sub { 
    my ($k1, $k2) = @_; 
    print "inside deep_keys, k1=$k1, k2=$k2\n"; 
}); 
+1

¡Me encanta esto! Gracias por la respuesta concisa y viable que funciona de la caja. –

1

Es bastante fácil si todo lo que quiere hacer funciona con valores, pero si desea operar con claves, necesita especificaciones sobre cómo los niveles serán recuperables.

a. Por ejemplo, puede especificar claves como "$level1_key.$level2_key.$level3_key" --o cualquier separador, que represente los niveles.

b. O podrías tener una lista de llaves.

Recomiendo lo último.

  • Nivel puede ser entendido por @$key_stack

  • y la clave más local es $key_stack->[-1].

  • La ruta puede ser reconstruido por: join('.', @$key\_stack)

Código:

use constant EMPTY_ARRAY => []; 
use strict;  
use Scalar::Util qw<reftype>; 

sub deep_keys (\%) { 
    sub deeper_keys { 
     my ($key_ref, $hash_ref) = @_; 
     return [ $key_ref, $hash_ref ] if reftype($hash_ref) ne 'HASH'; 
     my @results; 

     while (my ($key, $value) = each %$hash_ref) { 
      my $k = [ @{ $key_ref || EMPTY_ARRAY }, $key ]; 
      push @results, deeper_keys($k, $value); 
     } 
     return @results; 
    } 

    return deeper_keys(undef, shift); 
} 

foreach my $kv_pair (deep_keys %$f) { 
    my ($key_stack, $value) = @_; 
    ... 
} 

Esto ha sido probado en Perl 5.10.

2

Tenga en cuenta que las listas y hashes de Perl no tienen tienen dimensiones de y, por lo tanto, no pueden ser multidimensionales. Lo que tiene puede tener es un elemento hash que se establece para hacer referencia a otro hash o lista. Esto se puede usar para crear estructuras multidimensionales falsas.

Una vez que te das cuenta de esto, las cosas se vuelven fáciles. Por ejemplo:

sub f($) { 
    my $x = shift; 
    if(ref $x eq 'HASH') { 
    foreach(values %$x) { 
     f($_); 
    } 
    } elsif(ref $x eq 'ARRAY') { 
    foreach(@$x) { 
     f($_); 
    } 
    } 
} 

Agregue lo que sea necesario hacer además de atravesar la estructura, por supuesto.

Una buena manera de hacer lo que necesita es pasar una referencia de código para llamar desde el interior f. Al usar prototipos secundarios, podría incluso hacer que las llamadas se parezcan a las funciones grep y map de Perl.

1

Si está trabajando con los datos de los árboles ir más de dos niveles de profundidad, y se encuentra con ganas de caminar ese árbol, primero debe tener en cuenta que se va a hacer un montón de trabajo extra para ti si planeas volver a implementar todo lo que necesitas hacer manualmente en hashes de hash de hashes cuando hay muchas buenas alternativas disponibles (search CPAN for "Tree").

Sin saber cuáles son sus requisitos de datos en realidad, le señalaré ciegamente en tutorial for Tree::DAG_Node para comenzar.

Dicho esto, Axeman está en lo cierto, un hashwalk se realiza más fácilmente con recursion. He aquí un ejemplo para empezar si usted siente que es absolutamente necesario resolver su problema con los hashes de hashes de hashes:

 
#!/usr/bin/perl 
use strict; 
use warnings; 

my %hash = (
    "toplevel-1" => 
    { 
     "sublevel1a" => "value-1a", 
     "sublevel1b" => "value-1b" 
    }, 
    "toplevel-2" => 
    { 
     "sublevel1c" => 
     { 
      "value-1c.1" => "replacement-1c.1", 
      "value-1c.2" => "replacement-1c.2" 
     }, 
     "sublevel1d" => "value-1d" 
    } 
); 

hashwalk(\%hash); 

sub hashwalk 
{ 
    my ($element) = @_; 
    if(ref($element) =~ /HASH/) 
    { 
     foreach my $key (keys %$element) 
     { 
      print $key," => \n"; 
      hashwalk($$element{$key}); 
     } 
    } 
    else 
    { 
     print $element,"\n"; 
    } 
} 

es la salida:

 
toplevel-2 => 
sublevel1d => 
value-1d 
sublevel1c => 
value-1c.2 => 
replacement-1c.2 
value-1c.1 => 
replacement-1c.1 
toplevel-1 => 
sublevel1a => 
value-1a 
sublevel1b => 
value-1b 

Tenga en cuenta que no se puede predecir en qué orden los elementos hash se atravesarán a menos que usted vincule el hash a través de Tie :: IxHash o similar. De nuevo, si va a realizar tanto trabajo, le recomiendo un módulo de árbol.

1

No hay forma de obtener la semántica que describes porque foreach itera sobre una lista un elemento a la vez. Tendría que tener deep_keys devolver un LoL (lista de listas) en su lugar. Incluso eso no funciona en el caso general de una estructura de datos arbitraria. Podría haber diferentes niveles de sub hash, algunos de los niveles podrían ser ARRAY refs, etc.

La manera Perlish de hacer esto sería escribir una función que pueda recorrer una estructura de datos arbitraria y aplicar una devolución de llamada en cada "hoja" (es decir, valor no de referencia). bmdhacks' answer es un punto de partida. La función exacta podría variar dependiendo de lo que quisiera hacer en cada nivel. Es bastante sencillo si lo único que te importa son los valores de las hojas. Las cosas se complican más si te importan las claves, índices, etc. que te llevaron a la hoja.

2

También puede eludir matrices multidimensionales si siempre tiene todos los valores clave, o que simplemente no necesita acceder a los niveles individuales como matrices separadas:

$arr{"foo",1} = "one"; 
$arr{"bar",2} = "two"; 

while(($key, $value) = each(%arr)) 
{ 
    @keyValues = split($;, $key); 
    print "key = [", join(",", @keyValues), "] : value = [", $value, "]\n"; 
} 

Este utiliza el separador de subíndice ps como el separador de valores múltiples en la clave.

12

Primera etapa: No reinventar la rueda :)

Una rápida search on CPAN tiros el increíblemente útil Data::Walk. Definir una subrutina para procesar cada nodo, y que está clasificadas

use Data::Walk; 

my $data = { # some complex hash/array mess }; 

sub process { 
    print "current node $_\n"; 
} 

walk \&process, $data; 

Y Bob es tu tío. Tenga en cuenta que si desea pasar un hash para caminar, tendrá que pasarle una referencia (consulte perldoc perlref), de la siguiente manera (de lo contrario, intentará procesar sus claves hash también):

walk \&process, \%hash; 

Para una solución más completa (pero más difícil de encontrar a primera vista en CPAN), use Data::Visitor::Callback o su módulo principal; esto tiene la ventaja de brindarle un control más preciso de lo que hace, y (solo para crédito callejero adicional) escrito usando Moose.

+0

Tengo el mismo problema con este módulo que tengo con File :: Find: "... la función & wanted es una devolución de llamada genérica y no le dice a Data :: Walk si un elemento es 'querido' o no. De hecho, su valor de retorno es ignorado ". ¿Por qué no me deja decidir si desciendes o no un hash? – Axeman

+1

Puede filtrar la lista de cosas para procesar con la opción preprocess => sub {} arg - consulte los documentos para obtener más detalles. – Penfold

Cuestiones relacionadas