4

假设我有两个哈希。其中一个包含一组数据,这些数据只需要保留显示在另一个哈希中的内容。

例如

my %hash1 = ( 
        test1 => { inner1 => { more => "alpha", evenmore => "beta" } },
        test2 => { inner2 => { more => "charlie", somethingelse => "delta" } },
        test3 => { inner9999 => { ohlookmore => "golf", somethingelse => "foxtrot" } }
    );

my %hash2 = (
        major=> { test2 => "inner2",
              test3 => "inner3" }  );

我想做的是删除 hash1 中的整个子哈希,如果它不作为 hash2{major} 中的键/值存在,最好没有模块。"innerX" 中包含的信息无关紧要,它必须单独放置(除非要删除 subhash,否则它可以消失)。

在上面的示例中,执行此操作后,hash1 将如下所示:

my %hash1 = ( 
        test2 => { inner2 => { more => "charlie", somethingelse => "delta" } },
        );

它删除了 hash1{test1} 和 hash1{test3},因为它们与 hash2 中的任何内容都不匹配。

这是我目前尝试过的,但它不起作用。这也可能不是最安全的事情,因为我在尝试从中删除哈希时循环了它。但是我要删除每个应该没问题的?

这是我这样做的尝试,但是 perl 抱怨:

不能使用字符串(“inner1”)作为哈希引用,而“严格引用”在使用

while(my ($test, $inner) = each %hash1)
{
    if(exists $hash2{major}{$test}{$inner})
    {
        print "$test($inner) is in exists.\n";
    }
    else
    {
        print "Looks like $test($inner) does not exist, REMOVING.\n";
       #not to sure if $inner is needed to remove the whole entry
         delete ($hash1{$test}{$inner});
    } 
}
4

4 回答 4

5

你很亲密。请记住,这$hash2{major}{$test}是一个标量,而不是哈希引用。

#! /usr/bin/perl

use strict;
use warnings;

my %hash1 = ( 
  test1 => { inner1 => { more => "alpha", evenmore => "beta" } },
  test2 => { inner2 => { more => "charlie", somethingelse => "delta" } },
  test3 => { inner9999 => { ohlookmore => "golf", somethingelse => "foxtrot" } }
);

my %hash2 = (
  major => { test2 => "inner2",
             test3 => "inner3" }
);

foreach my $k (keys %hash1) {
  my $delete = 1;
  foreach my $inner (keys %{ $hash1{$k} }) {
    $delete = 0, last if exists $hash2{major}{$k} &&
                                $hash2{major}{$k} eq $inner;
  }
  delete $hash1{$k} if $delete;
}

use Data::Dumper;
$Data::Dumper::Indent = 1;
print Dumper \%hash1;

开头的那行$delete = 0, ...有点可爱。它相当于$delete = 0; last;在另一个条件内,但它已经嵌套了两次。不想制作套娃,我使用了语句修饰符,但顾名思义,它修改了单个语句。

这就是Perl 的逗号运算符的用武之地:

二进制,是逗号运算符。在标量上下文中,它计算其左参数,丢弃该值,然后计算其右参数并返回该值。这就像 C 的逗号运算符。

在这种情况下,左边的参数是表达式$delete = 0,右边的参数是last

有条件的可能看起来不必要的挑剔,但是

... if $hash2{major}{$k} eq $inner;

%hash2在探测(例如,test1/inner1)中未提及的测试时产生未定义值警告。使用

.. if $hash2{major}{$k} && $hash2{major}{$k} eq $inner;

%hash2如果它的“内部名称”是错误值(例如字符串),则会错误地删除中提到的测试"0"。是的,exists在这里使用可能是不必要的繁琐,但不知道你的实际哈希键,我选择了保守的路线。

输出:

$VAR1 = {
  'test2' => {
    'inner2' => {
      'somethingelse' => 'delta',
      '更多' => '查理'
    }
  }
};

尽管您没有违反它,但请注意以下与 using 相关的警告each

如果您在迭代哈希时添加或删除元素,您可能会跳过或重复条目,所以不要这样做。例外:删除最近返回的项目总是安全的each,这意味着以下代码将起作用:

    while (($key, $value) = each %hash) {
      print $key, "\n";
      delete $hash{$key};   # This is safe
    }

更新:像数组一样搜索哈希(通过说“......线性而不是对数”来打动你的 CS 书呆子朋友)是一个危险信号,上面的代码就是这样做的。一个更好的方法,结果类似于 Penfold 的答案,是

%hash1 = map +($_ => $hash1{$_}),
         grep exists $hash2{major}{$_} &&
              exists $hash1{$_}{ $hash2{major}{$_} },
         keys %hash1;

以漂亮的声明式风格,它描述了所需的内容%hash1,即

  1. 的一级键%hash1应该在$hash2{major}, 和
  2. $hash2{major}每个一级键对应的值本身应该是该键的子键%hash1

(哇,眼花缭乱。我们需要多个英文占位符变量!)

一元加号+($_ => $hash1{$_})为可怜的解析器消除歧义,因此它知道我们希望将表达式视为“对”。有关其他可能需要的情况,请参阅perlfunc 文档的末尾。map

于 2010-04-02T21:03:26.650 回答
4

您可以将其作为单行来执行,这一切都是因为 delete() 将采用一组键。这并不像我最初想象的那么容易,但现在我已经正确地阅读了这个问题......

delete @hash1{ 
        grep(
            !(
              exists($hash2{major}->{$_}) 
                && 
              exists( $hash1{$_}->{ $hash2{major}->{$_} } )
            ),
            keys %hash1
        )
    };
于 2010-04-02T21:29:23.697 回答
1

这就是我会这样做的方式:(第三次尝试的魅力)

foreach ( map { [ $_ => $hash2{major}{$_} ] } keys %hash1 ) { 
    my ( $key, $value ) = @$_;
    if ( defined $value and my $new_value = $hash1{$key}{$value} ) { 
        $hash1{$key} = $new_value;
    }
    else { 
        delete $hash1{$key};
    }
}
于 2010-04-02T21:05:46.223 回答
1
# This is the actual hash we want to iterate over.
my $keepers = $hash2{major};

%hash1 = map { $_ => $hash1{$_} }  # existing key and hash contents in %hash1
             grep { exists $keepers->{$_} and               # key there?
                    exists $hash1{$_}->{ $keepers->{$_} } } # key in hash there?
             (keys %hash1);        # All the keys we might care about

这是有效的,因为我们基本上在三个独立的阶段中列出了我们想要/不想要的东西:

  1. 键调用一步获取 hash1 中的所有键。
  2. grep 生成(作为一个步骤)与我们的标准匹配的键列表。
  3. 该映射生成(作为一个步骤)一组我们想要的键和值。

这样,在我们准备好之前,我们永远不会更改主哈希。如果 %hash1 包含许多键,我们将使用大量内存。如果你担心这一点,你会做这样的事情:

# Initialization as before ...

use File::Temp qw(tempfile);

my ($fh, $file) = tempfile();
my $keepers = $hash2{major};

print $fh "$_\n" for (keys %hash1);
close $fh;
open $fh, "<", $file or die "can't reopen tempfile $file: $!\n";
while ( defined ($_ = <$fh>) ) {
  chomp;
  delete $hash1{$_} 
    unless exists $keepers->{$_} and 
           exists $hash1{$_}->{ $keepers->{$_} }; 
}

这个之所以有效,是因为我们不是遍历散列,而是遍历其键的存储副本。

于 2010-04-02T21:53:25.543 回答