Pregunta

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

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

en lugar de

foreach $x (keys %f) 
    {
    foreach $y (keys %{$f->{$x}) 
    {
    }
}
¿Fue útil?

Solución

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

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";
});

Otros consejos

Etapa uno: no reinventes la rueda :)

Una rápida búsqueda en CPAN arroja la increíblemente útil Data :: Walk . Defina una subrutina para procesar cada nodo, y estará ordenado

use Data::Walk;

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

sub process {
   print "current node 

Etapa uno: no reinventes la rueda :)

Una rápida búsqueda en CPAN arroja la increíblemente útil Data :: Walk . Defina una subrutina para procesar cada nodo, y estará ordenado

walk \&process, \%hash;

Y Bob es tu tío. Tenga en cuenta que si desea pasarle un hash para caminar, deberá pasarle una referencia (consulte perldoc perlref ), de la siguiente manera (de lo contrario, ¡también intentará procesar sus claves 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 mejor control de lo que hace, y (solo para credenciales adicionales) se escribe usando Alce.

\n"; } walk \&process, $data;

Y Bob es tu tío. Tenga en cuenta que si desea pasarle un hash para caminar, deberá pasarle una referencia (consulte perldoc perlref ), de la siguiente manera (de lo contrario, ¡también intentará procesar sus claves 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 mejor control de lo que hace, y (solo para credenciales adicionales) se escribe usando Alce.

Esto me suena como si Data :: Diver o Data :: Visitor son buenos enfoques para usted.

Tenga en cuenta que las listas y los hashes de Perl no tienen dimensiones y, por lo tanto, no pueden ser multidimensionales. Lo que puede tener es un elemento hash que está configurado para hacer referencia a otro hash o lista. Esto se puede utilizar 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(

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

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

<*>

Agregue cualquier otra cosa que deba hacerse además de atravesar la estructura, por supuesto.

Una forma ingeniosa de hacer lo que necesita es pasar una referencia de código para que se llame desde dentro de f. Mediante el uso de prototipos secundarios, incluso podría hacer que las llamadas se parezcan a las funciones grep y map de Perl.

); } } elsif( ref $x eq 'ARRAY' ) { foreach( @$x ) { f(

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

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

<*>

Agregue cualquier otra cosa que deba hacerse además de atravesar la estructura, por supuesto.

Una forma ingeniosa de hacer lo que necesita es pasar una referencia de código para que se llame desde dentro de f. Mediante el uso de prototipos secundarios, incluso podría hacer que las llamadas se parezcan a las funciones grep y map de Perl.

); } } }

Agregue cualquier otra cosa que deba hacerse además de atravesar la estructura, por supuesto.

Una forma ingeniosa de hacer lo que necesita es pasar una referencia de código para que se llame desde dentro de f. Mediante el uso de prototipos secundarios, incluso podría hacer que las llamadas se parezcan a las funciones grep y map de Perl.

También puede falsificar matrices multidimensionales si siempre tiene todos los valores clave, o 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";
}

Esto utiliza el separador de subíndices " $; " como separador de múltiples valores en la clave.

No hay forma de obtener la semántica que describe porque foreach itera sobre una lista un elemento a la vez. Tendría que hacer que deep_keys devuelva 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 forma 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 sin referencia). la respuesta de bmdhacks es un punto de partida. La función exacta variaría dependiendo de lo que quisieras hacer en cada nivel. Es bastante sencillo si lo único que te importa es el valor de la hoja. Las cosas se vuelven más complicadas si te importan las claves, los índices, etc. que te llevaron a la hoja.

Es bastante fácil si todo lo que quiere hacer es operar con valores, pero si quiere operar con teclas, necesita especificaciones de 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 este último.

  • El nivel puede entenderse por @$key_stack

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

  • La ruta se puede reconstruir mediante: 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.

Si está trabajando con datos de árbol que van a más de dos niveles de profundidad y desea caminar por ese árbol, primero debe considerar que va a hacer mucho trabajo adicional para usted si planea reimplementar todo debe hacer manualmente los hashes de hashes de hashes cuando hay muchas alternativas buenas disponibles ( busque CPAN para " Árbol " ).

Sin saber cuáles son realmente tus requisitos de datos, te orientaré ciegamente hacia un tutorial para Tree :: DAG_Node para comenzar.

Dicho esto, Axeman es correcto, un hashwalk se hace más fácilmente con recursividad. Este es un ejemplo para comenzar, si cree que debe resolver su problema con hashes of hashes of 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";
    }
}

Saldrá:

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 PUEDE predecir en qué orden se desplazarán los elementos hash a menos que vincule el hash a través de Tie :: IxHash o similar & # 8212; de nuevo, si vas a pasar por tanto trabajo, te recomiendo un módulo de árbol.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top