Domanda

Se hai un hash (o riferimento a un hash) in perl con molte dimensioni e vuoi iterare su tutti i valori, qual è il modo migliore per farlo. In altre parole, se abbiamo $ f- > {$ x} {$ y}, voglio qualcosa del tipo

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

anziché

foreach $x (keys %f) 
    {
    foreach $y (keys %{$f->{$x}) 
    {
    }
}
È stato utile?

Soluzione

Ecco un'opzione. Questo funziona con hash arbitrariamente profondi:

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

Altri suggerimenti

Fase uno: non reinventare la ruota :)

Una rapida ricerca su CPAN solleva l'incredibilmente utile Dati :: Walk . Definisci una subroutine per elaborare ciascun nodo e sei ordinato

use Data::Walk;

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

sub process {
   print "current node 

Fase uno: non reinventare la ruota :)

Una rapida ricerca su CPAN solleva l'incredibilmente utile Dati :: Walk . Definisci una subroutine per elaborare ciascun nodo e sei ordinato

walk \&process, \%hash;

E Bob è tuo zio. Nota che se vuoi passare un hash per camminare, dovrai passare un riferimento ad esso (vedi perldoc perlref ), come segue (altrimenti tenterà di elaborare anche le tue chiavi hash!):

<*>

Per una soluzione più completa (ma più difficile da trovare a prima vista in CPAN), utilizzare Data :: Visitor :: Callback o il suo modulo genitore - questo ha il vantaggio di darti un controllo più preciso di ciò che fai e (solo per credito extra) è scritto usando Moose.

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

E Bob è tuo zio. Nota che se vuoi passare un hash per camminare, dovrai passare un riferimento ad esso (vedi perldoc perlref ), come segue (altrimenti tenterà di elaborare anche le tue chiavi hash!):

<*>

Per una soluzione più completa (ma più difficile da trovare a prima vista in CPAN), utilizzare Data :: Visitor :: Callback o il suo modulo genitore - questo ha il vantaggio di darti un controllo più preciso di ciò che fai e (solo per credito extra) è scritto usando Moose.

Mi sembra che Data :: Diver o Dati :: Visitatore sono buoni approcci per te.

Tieni presente che gli elenchi e gli hash di Perl non hanno dimensioni e quindi non possono essere multidimensionali. Quello che puoi avere è un elemento hash impostato per fare riferimento a un altro hash o elenco. Questo può essere usato per creare false strutture multidimensionali.

Una volta realizzato questo, le cose diventano facili. Ad esempio:

sub f($) {
  my $x = shift;
  if( ref $x eq 'HASH' ) {
    foreach( values %$x ) {
      f(

Tieni presente che gli elenchi e gli hash di Perl non hanno dimensioni e quindi non possono essere multidimensionali. Quello che puoi avere è un elemento hash impostato per fare riferimento a un altro hash o elenco. Questo può essere usato per creare false strutture multidimensionali.

Una volta realizzato questo, le cose diventano facili. Ad esempio:

<*>

Aggiungi tutto ciò che deve essere fatto oltre a attraversare la struttura, ovviamente.

Un modo elegante per fare ciò di cui hai bisogno è passare un riferimento di codice da chiamare dall'interno f. Usando il prototipo secondario puoi persino far apparire le chiamate come funzioni grep e map di Perl.

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

Tieni presente che gli elenchi e gli hash di Perl non hanno dimensioni e quindi non possono essere multidimensionali. Quello che puoi avere è un elemento hash impostato per fare riferimento a un altro hash o elenco. Questo può essere usato per creare false strutture multidimensionali.

Una volta realizzato questo, le cose diventano facili. Ad esempio:

<*>

Aggiungi tutto ciò che deve essere fatto oltre a attraversare la struttura, ovviamente.

Un modo elegante per fare ciò di cui hai bisogno è passare un riferimento di codice da chiamare dall'interno f. Usando il prototipo secondario puoi persino far apparire le chiamate come funzioni grep e map di Perl.

); } } }

Aggiungi tutto ciò che deve essere fatto oltre a attraversare la struttura, ovviamente.

Un modo elegante per fare ciò di cui hai bisogno è passare un riferimento di codice da chiamare dall'interno f. Usando il prototipo secondario puoi persino far apparire le chiamate come funzioni grep e map di Perl.

Puoi anche modificare le matrici multidimensionali se hai sempre tutti i valori chiave o se non devi accedere ai singoli livelli come matrici separate:

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

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

Questo utilizza il separatore di pedici " $; " come separatore per più valori nella chiave.

Non c'è modo di ottenere la semantica che descrivi perché foreach scorre su un elenco un elemento alla volta. Dovresti invece avere deep_keys per restituire un LoL (elenco di liste). Anche questo non funziona nel caso generale di una struttura di dati arbitraria. Potrebbero esserci vari livelli di hash secondari, alcuni dei livelli potrebbero essere ref ARRAY, ecc.

Il modo perlish di farlo sarebbe quello di scrivere una funzione che può camminare su una struttura di dati arbitraria e applicare un callback ad ogni "foglia" (ovvero valore non di riferimento). La la risposta di bmdhacks è un punto di partenza. La funzione esatta varierebbe a seconda di ciò che si voleva fare ad ogni livello. È abbastanza semplice se tutto ciò che ti interessa sono i valori delle foglie. Le cose si complicano se ti preoccupi delle chiavi, degli indici, ecc. Che ti hanno portato alla foglia.

È abbastanza facile se tutto ciò che vuoi fare è operare sui valori, ma se vuoi operare sui tasti, hai bisogno di specifiche su come i livelli saranno recuperabili.

a. Ad esempio, puoi specificare le chiavi come " $ level1_key. $ Level2_key. $ Level3_key " - o qualsiasi separatore, che rappresenta i livelli.

b. Oppure potresti avere un elenco di chiavi.

Raccomando quest'ultimo.

  • Il livello può essere compreso da @$key_stack

  • e la chiave più locale è $key_stack->[-1[.

  • Il percorso può essere ricostruito da: join ('.', @ $ key \ _stack)

Codice:

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 ) = @_;
    ...
}

Questo è stato testato in Perl 5.10.

Se stai lavorando con dati sugli alberi che superano i due livelli di profondità e ti trovi a voler camminare su quell'albero, dovresti prima considerare che farai molto lavoro extra per te stesso se pensi di reimplementare tutto devi fare manualmente gli hash degli hash degli hash quando ci sono molte buone alternative disponibili ( cerca CPAN per " Albero " ).

Non sapendo quali siano effettivamente i tuoi requisiti relativi ai dati, ti indicherò ciecamente un tutorial per Tree :: DAG_Node per iniziare.

Detto questo, Axeman ha ragione, un hashwalk si fa più facilmente con la ricorsione. Ecco un esempio per iniziare se ritieni di dover assolutamente risolvere il problema con gli hash degli hash degli hash:

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

Verrà emesso:

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

Nota che NON PUOI prevedere in quale ordine verranno attraversati gli elementi hash a meno che tu non leghi l'hash tramite Tie :: IxHash o simili - di nuovo, se hai intenzione di passare tutto quel lavoro, ti consiglio un modulo ad albero .

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top