Perl的"存在"可以修改数据结构值吗?

dan*_*s87 14 perl

我有一个嵌套的哈希表,如下所示:

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

我对此数据结构的目标是根据是否存在某些哈希表来生成不同的值.例如,

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";
}
Run Code Online (Sandbox Code Playgroud)

我遇到的行为如下.我的$ val ="val1"时有一个实例; 我的$ file ="C/Dc"; 我的$ funct ="funct3";

此时,返回值为"return2".这些是我对Perl调试器的观察:

  1. 在mysub中首先打破"if"
  2. 打印p $ proxToBugs {"val1"} {"C/Dc"} ==>返回空白行.好的.继续,跳过此"if".
  3. 继续并在mysub中的第二个"if"中断
  4. 打印p $ proxToBugs {"val1"} {"C/Dc"} ==>返回"HASH(0x ...)".WTF时刻.函数返回"return2".

这告诉我运行第一个如果修改了数据结构,它允许第二个if传递,实际上它不应该.我正在运行的功能与上面显示的功能相同; 这个只是消毒了.有人对我有解释吗?:)

小智 21

是.这是因为自动化.请参阅exists文档底部:

虽然大多数深度嵌套的数组或散列只是因为它的存在被测试而不会存在,但任何介入的数组[autvivified数组或散列]都会[弹出存在].因此,由于上面的$ key元素的存在测试,$ ref - > {"A"}和$ ref - > {"A"} - > {"B"}将会存在.这在使用箭头操作符的任何地方都会发生

其中"......测试上面的$ key元素......"是指:

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

快乐的编码.

  • 确认......在Perl中我还有很多需要学习的东西.这件事让我疯了半个多小时.谢谢! (2认同)

Cha*_*ens 9

正如pst正确指出的那样,这就是自动更新.至少有两种方法可以避免它.第一个(在我的经验中最常见)是在每个级别进行测试:

if (
    exists $h{a}       and
    exists $h{a}{b}    and
    exists $h{a}{b}{c}
) {
    ...
}
Run Code Online (Sandbox Code Playgroud)

如果较早的级别不存在,则短路性质and导致第二次和第三次调用exists不被执行.

最新的解决方案是autovivificationpragma(可从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;
}
Run Code Online (Sandbox Code Playgroud)

在评论中提到的第三种方法具有核心(如第一个例子)和不重复exists函数调用的好处; 但是,我认为它是以牺牲可读性为代价的:

if (exists ${ ${ $h{a} || {} }{b} || {} }{c}) {
    ...
}
Run Code Online (Sandbox Code Playgroud)

它的工作原理是用hashref替换不存在的任何级别来进行自动生成.if语句执行完毕后,将丢弃这些hashref .我们再次看到了短路逻辑的价值.

当然,所有这三种方法都假设哈希预期要保存的数据,一个更健壮的方法包括调用refreftype取决于你想如何对待对象(有第三个选项考虑到超载的类哈希索引运算符,但我不记得它的名字):

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}
) {
    ...
}
Run Code Online (Sandbox Code Playgroud)

在评论中,pst询问是否myExists($ref,"a","b","c")存在类似的东西.我确信CPAN中有一个模块可以做类似的事情,但我不知道.有太多边缘情况让我觉得有用,但一个简单的实现将是:

#!/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;
Run Code Online (Sandbox Code Playgroud)


bri*_*foy 8

如果要关闭自动生成,可以使用autovivification pragma 以词法方式执行此操作:

 {
 no autovivification;

 if( exists $hash{A}{B}{$key} ) { ... }
 }
Run Code Online (Sandbox Code Playgroud)

我在The Effective Perler中写了更多关于这个的内容,因为当你不想要它时关闭自动生成功能.