perl hash autovivification

¿Cómo puedo verificar si existe una clave en un hash profundo de Perl?



autovivification (5)

Bastante feo, pero si $ ref es una expresión complicada que no quieres usar en pruebas de repetición existen:

if ( exists ${ ${ ${ $ref || {} }{A} || {} }{B} || {} }{key} ) {

Si entiendo correctamente , llamar a if (exists $ref->{A}->{B}->{$key}) { ... } surgirá a la existencia $ref->{A} y $ref->{A}->{B} incluso si no existieron antes del if !

Esto parece muy indeseado. Entonces, ¿cómo debo verificar si existe una clave hash "profunda"?


Eche un vistazo a Data::Diver . P.ej:

use Data::Diver qw(Dive); my $ref = { A => { foo => "bar" } }; my $value1 = Dive($ref, qw(A B), $key); my $value2 = Dive($ref, qw(A foo));


Es mucho mejor usar algo como el módulo de autovivification para desactivar esa función o usar Data::Diver . Sin embargo, esta es una de las tareas simples que esperaría que un programador supiera cómo hacer por sí mismo. Incluso si no usa esta técnica aquí, debe saberlo por otros problemas. Esto es esencialmente lo que Data::Diver está haciendo una vez que quitas la interfaz.

Esto es fácil una vez que obtiene el truco de recorrer una estructura de datos (si no desea usar un módulo que lo haga por usted). En mi ejemplo, creo una subrutina check_hash que toma una referencia de hash y una referencia de matriz de claves para verificar. Comprueba un nivel a la vez. Si la clave no está allí, no devuelve nada. Si la clave está allí, corta el hash solo a esa parte de la ruta e intenta nuevamente con la siguiente tecla. El truco es que $hash es siempre la siguiente parte del árbol para verificar. Puse el exists en una eval en caso de que el siguiente nivel no sea una referencia hash. El truco es no fallar si el valor hash al final de la ruta es algún tipo de valor falso. Aquí está la parte importante de la tarea:

sub check_hash { my( $hash, $keys ) = @_; return unless @$keys; foreach my $key ( @$keys ) { return unless eval { exists $hash->{$key} }; $hash = $hash->{$key}; } return 1; }

No te asustes por todo el código en el siguiente bit. La parte importante es solo la subrutina check_hash . Todo lo demás es prueba y demostración:

#!perl use strict; use warnings; use 5.010; sub check_hash { my( $hash, $keys ) = @_; return unless @$keys; foreach my $key ( @$keys ) { return unless eval { exists $hash->{$key} }; $hash = $hash->{$key}; } return 1; } my %hash = ( a => { b => { c => { d => { e => { f => ''foo!'', }, f => ''foo!'', }, }, f => ''foo!'', g => ''goo!'', h => 0, }, f => [ qw( foo goo moo ) ], g => undef, }, f => sub { ''foo!'' }, ); my @paths = ( [ qw( a b c d ) ], # true [ qw( a b c d e f ) ], # true [ qw( b c d ) ], # false [ qw( f b c ) ], # false [ qw( a f ) ], # true [ qw( a f g ) ], # false [ qw( a g ) ], # true [ qw( a b h ) ], # false [ qw( a ) ], # true [ qw( ) ], # false ); say Dumper( /%hash ); use Data::Dumper; # just to remember the structure foreach my $path ( @paths ) { printf "%-12s --> %s/n", join( ".", @$path ), check_hash( /%hash, $path ) ? ''true'' : ''false''; }

Aquí está la salida (menos el volcado de datos):

a.b.c.d --> true a.b.c.d.e.f --> true b.c.d --> false f.b.c --> false a.f --> true a.f.g --> false a.g --> true a.b.h --> true a --> true --> false

Ahora, es posible que desee tener algún otro cheque en lugar de exists . Tal vez quiera verificar que el valor en la ruta elegida sea verdadero, o una cadena, u otra referencia hash, o lo que sea. Solo se trata de proporcionar el cheque correcto una vez que haya verificado que existe la ruta. En este ejemplo, paso una referencia de subrutina que verificará el valor que dejé. Puedo verificar cualquier cosa que me guste:

#!perl use strict; use warnings; use 5.010; sub check_hash { my( $hash, $sub, $keys ) = @_; return unless @$keys; foreach my $key ( @$keys ) { return unless eval { exists $hash->{$key} }; $hash = $hash->{$key}; } return $sub->( $hash ); } my %hash = ( a => { b => { c => { d => { e => { f => ''foo!'', }, f => ''foo!'', }, }, f => ''foo!'', g => ''goo!'', h => 0, }, f => [ qw( foo goo moo ) ], g => undef, }, f => sub { ''foo!'' }, ); my %subs = ( hash_ref => sub { ref $_[0] eq ref {} }, array_ref => sub { ref $_[0] eq ref [] }, true => sub { ! ref $_[0] && $_[0] }, false => sub { ! ref $_[0] && ! $_[0] }, exist => sub { 1 }, foo => sub { $_[0] eq ''foo!'' }, ''undef'' => sub { ! defined $_[0] }, ); my @paths = ( [ exist => qw( a b c d ) ], # true [ hash_ref => qw( a b c d ) ], # true [ foo => qw( a b c d ) ], # false [ foo => qw( a b c d e f ) ], # true [ exist => qw( b c d ) ], # false [ exist => qw( f b c ) ], # false [ array_ref => qw( a f ) ], # true [ exist => qw( a f g ) ], # false [ ''undef'' => qw( a g ) ], # true [ exist => qw( a b h ) ], # false [ hash_ref => qw( a ) ], # true [ exist => qw( ) ], # false ); say Dumper( /%hash ); use Data::Dumper; # just to remember the structure foreach my $path ( @paths ) { my $sub_name = shift @$path; my $sub = $subs{$sub_name}; printf "%10s --> %-12s --> %s/n", $sub_name, join( ".", @$path ), check_hash( /%hash, $sub, $path ) ? ''true'' : ''false''; }

Y su resultado:

exist --> a.b.c.d --> true hash_ref --> a.b.c.d --> true foo --> a.b.c.d --> false foo --> a.b.c.d.e.f --> true exist --> b.c.d --> false exist --> f.b.c --> false array_ref --> a.f --> true exist --> a.f.g --> false undef --> a.g --> true exist --> a.b.h --> true hash_ref --> a --> true exist --> --> false


Puede usar el pragma de autovivification para desactivar la creación automática de referencias:

use strict; use warnings; no autovivification; my %foo; print "yes/n" if exists $foo{bar}{baz}{quux}; print join '', '', keys %foo;

También es léxico, lo que significa que solo lo desactivará dentro del ámbito en que lo especifique.


Verifique la exist cada nivel antes de mirar el nivel superior.

if (exists $ref->{A} and exists $ref->{A}{B} and exists $ref->{A}{B}{$key}) { }

Si te parece molesto, siempre puedes mirar CPAN . Por ejemplo, hay Hash::NoVivify .