2011-07-31 16 views
14

Tengo una tabla resumen anidada que tiene este aspecto:¿Puede "existe" Can Perl modificar los valores de la estructura de datos?

my %myhash = (
    "val1" => { 
     "A/B.c" => { 
      "funct1" => 1 
     } 
    }, 
    "val2" => { 
     "C/D.c" => { 
      "funct2" => 1 
     } 
    } 
) 

Mi objetivo con esta estructura de datos es producir valores diferentes en función de si existen ciertas tablas hash. Por ejemplo,

sub mysub 
{ 
    my $val = shift; 
    my $file = shift; 
    my $funct = shift; 

    if (exists $myhash{$val}{$file}{$funct}) { 
     return "return1"; 
    } 
    if (exists $myhash{$val}{$file}) { 
     return "return2"; 
    } 
    return "return3"; 
} 

El comportamiento que encuentro es el siguiente. Tengo una instancia en el tiempo cuando my $ val = "val1"; my $ file = "C/D.c"; my $ funct = "funct3";

En este punto en el tiempo, el valor de retorno me sale "return2". Estas son mis observaciones con el depurador de Perl:

  1. rotura en un primer momento "si" en mysub
  2. Imprimir p $ {proxToBugs "val1" {} "C/D.C."} ==> Devuelve línea en blanco. Bueno. Continúa y se omite este "si".
  3. Continuar y romper en el segundo "si" en mysub
  4. Imprimir p $ proxToBugs {"val1"} {"C/D.c"} ==> Devuelve "HASH (0x ...)". WTF momento. La función devuelve "return2".

Esto me dice que ejecutar el primero si se modificó la estructura de datos, lo que permite que el segundo pase si en realidad no debería. La función que estoy ejecutando es idéntica a la función que se muestra arriba; este solo está desinfectado ¿Alguien tiene una explicación para mí? :)

Respuesta

21

Sí. Esto se debe a autovivification. Ver la parte inferior de la documentación exists:

Aunque la matriz en su mayoría profundamente anidada o de hash no surgirán a la existencia sólo porque su existencia se probó, cualquier los que intervienen [matrices o hashes autovivified] voluntad [resorte en existencia]. Por lo tanto, $ ref -> {"A"} y $ ref -> {"A"} -> {"B"} aparecerán debido a la prueba de existencia del elemento $ key anterior. Esto sucede en cualquier lugar se utiliza el operador de flecha ...

Donde "... prueba para el elemento clave $ por encima de ..." se refiere a:

if (exists $ref->{A}->{B}->{$key}) { } 
if (exists $hash{A}{B}{$key})  { } # same idea, implicit arrow 

de codificación feliz.

+2

Confirmado ... Todavía tengo mucho que aprender en Perl I do. Esto me estaba volviendo loco durante más de media hora. ¡Gracias! – danns87

9

Como pst correctamente señala, esto es autovivificación. Hay al menos dos formas de evitarlo.La primera (y más común en mi experiencia) es probar en cada nivel:

if (
    exists $h{a}  and 
    exists $h{a}{b} and 
    exists $h{a}{b}{c} 
) { 
    ... 
} 

La naturaleza de cortocircuito de and hace que las llamadas segunda y tercera a exists a no ser ejecutado si los niveles anteriores no lo hacen existe.

Una solución más reciente es la autovivification pragma (disponible de CPAN):

#!/usr/bin/perl 

use strict; 
use warnings; 

use Data::Dumper; 

$Data::Dumper::Useqq = 1; 

{ 
    my %h; 

    if (exists $h{a}{b}{c}) { 
     print "impossible, it is empty\n"; 
    } 

    print Dumper \%h; 
} 

{ 
    no autovivification; 

    my %h; 

    if (exists $h{a}{b}{c}) { 
     print "impossible, it is empty\n"; 
    } 

    print Dumper \%h; 
} 

Un tercer método que ysth menciones en los comentarios tiene los beneficios de ser en el núcleo (como el primer ejemplo) y de no repitiendo la llamada a la función exists; Sin embargo, creo que lo hace a expensas de la legibilidad:

if (exists ${ ${ $h{a} || {} }{b} || {} }{c}) { 
    ... 
} 

Funciona mediante la sustitución de cualquier nivel que no existe con una hashref tomar el autovivification. Estos hashrefs se descartarán después de que se haya ejecutado la instrucción if. Nuevamente vemos el valor de la lógica de cortocircuito.

Por supuesto, los tres de estos métodos hace una suposición sobre los datos que se espera que el hash de sostener, un método más robusto incluye llamadas a ref o reftype dependiendo de cómo desea tratar los objetos (hay una tercera opción que toma en cuenta las clases que sobrecargan el operador hash de indexación, pero no puedo recordar su nombre):

if (
    exists $h{a}   and 
    ref $h{a} eq ref {} and 
    exists $h{a}   and 
    ref $h{a}{b} eq ref {} and 
    exists $h{a}{b}{c} 
) { 
    ... 
} 

En los comentarios, pidió pst si existe algo así como myExists($ref,"a","b","c"). Estoy seguro de que hay un módulo en CPAN que hace algo así, pero no lo conozco. Hay demasiados casos extremos para mí que me parecen útiles, pero una implementación simple sería:

#!/usr/bin/perl 

use strict; 
use warnings; 

use Data::Dumper; 

sub safe_exists { 
    my ($ref, @keys) = @_; 

    for my $k (@keys) { 
     return 0 unless ref $ref eq ref {} and exists $ref->{$k}; 
     $ref = $ref->{$k}; 
    } 
    return 1; 
} 

my %h = (
    a => { 
     b => { 
      c => 5, 
     }, 
    }, 
); 

unless (safe_exists \%h, qw/x y z/) { 
    print "x/y/z doesn't exist\n"; 
} 

unless (safe_exists \%h, qw/a b c d/) { 
    print "a/b/c/d doesn't exist\n"; 
} 

if (safe_exists \%h, qw/a b c/) { 
    print "a/b/c does exist\n"; 
} 

print Dumper \%h; 
+1

tercera vía: 'existe $ {$ {$ myhash {$ val} || {}} {$ archivo} || {}} {$ funct} ' – ysth

+0

Y una forma de' myExists ($ ref, "a", "b", "c") '? :) –

Cuestiones relacionadas