如何根据另一个哈希的键/值删除[sub]哈希?

时间:2010-04-02 20:41:19

标签: perl hash perl-data-structures

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

e.g。

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中的整个subhash,如果它不作为hash2 {major}中的键/值存在,最好没有模块。 “innerX”中包含的信息无关紧要,只需要保持不变(除非要删除subhash然后它就会消失)。

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

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

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

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

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

使用“严格参考”时,不能使用字符串(“inner1”)作为HASH引用
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 个答案:

答案 0 :(得分: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;,但它已经嵌套了两次。不想构建matryoshka doll,我使用statement modifier,但顾名思义,它会修改单个语句。

这就是Perl's comma operator的来源:

  

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

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

条件似乎不必要地挑剔,但

... if $hash2{major}{$k} eq $inner;
在探测%hash2中未提及的测试时,

会产生未定义值警告(例如,test1 / inner1)。使用

.. if $hash2{major}{$k} && $hash2{major}{$k} eq $inner;
如果{“1}}中的”内部名称“是错误值,例如字符串%hash2

将错误地删除"0"中提到的测试。是的,在这里使用exists可能是不必要的挑剔,但不知道你的实际哈希键,我选择了保守的路线。

输出:

$VAR1 = {
  'test2' => {
    'inner2' => {
      'somethingelse' => 'delta',
      'more' => 'charlie'
    }
  }
};

虽然您没有违反此规定,但请注意以下与使用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. 中提及
  3. $hash2{major}对应每个第一级密钥的值本身应该是%hash1
  4. 中该密钥的子密钥

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

    +($_ => $hash1{$_})中的一元加号消除了可怜的解析器的歧义,因此它知道我们希望将表达式视为“对”。如果有必要,请参阅perlfunc documentation on map的结尾。

答案 1 :(得分:4)

你可以把它作为一个单行,所有因为delete()将采用一组键。它并不像我最初想的那么容易,但现在我已经正确地阅读了这个问题......

delete @hash1{ 
        grep(
            !(
              exists($hash2{major}->{$_}) 
                && 
              exists( $hash1{$_}->{ $hash2{major}->{$_} } )
            ),
            keys %hash1
        )
    };

答案 2 :(得分: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};
    }
}

答案 3 :(得分: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. 地图生成(作为一步)一组我们想要的键和值。
  4. 这样我们就不会改变主哈希,直到我们准备好这样做。如果%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->{$_} }; 
    }
    

    这个可行,因为我们不会遍历哈希,而是遍历其密钥的存储副本。