2010-04-02 82 views
4

让我们假设我有两个哈希值。其中一个包含一组数据,只需要保留其他散列中显示的内容。如何根据另一个散列的键/值删除一个[sub]散列?

例如

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 {}大键/值存在,最好不模块。包含在“innerX”中的信息无关紧要,它只是单独存在(除非删除子哈希然后它可以消失)。

在上面这个操作后,预制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}); 
    } 
} 

回答

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; 

会错误地删除%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书呆子朋友说“...线性而非对数”)是红旗和上面的代码就是这样做的。一个更好的办法,这原来是相似的奔富的回答,是

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

在漂亮的声明样式,它描述的%hash1到所需的内容,即

  1. %hash1一级键应该是在$hash2{major}提到的,和
  2. $hash2{major}对应于每个第一级密钥值应该本身是项的子项回%hash1

(哇,令人目不暇接。 )

+($_ => $hash1{$_})的一元加号为歧义解析器消歧,因此它知道我们希望表达式被视为“对”。对于其他情况,请参见perlfunc documentation on map的结尾,这可能是必要。

+0

是否有可能得到这一行的解释: $ delete = 0,如果存在则为last $ hash2 {major} {$ k} && $ hash2 {major} {$ k} eq $ inner; 我有点理解它,但我真的被'''和'last用法抛弃了。 – Zack 2010-04-03 21:38:13

+0

@Zack感谢您的选中标记!更新答案中提供的解释以及额外的奖励。 – 2010-04-03 23:42:05

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}; 
    } 
} 
4

你可以做到这一点作为一个班轮,都是因为删除()将键数组。它并不像我最初想的那么简单,但现在我读过问题正确...

delete @hash1{ 
     grep(
      !(
       exists($hash2{major}->{$_}) 
       && 
       exists($hash1{$_}->{ $hash2{major}->{$_} }) 
      ), 
      keys %hash1 
     ) 
    }; 
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->{$_} }; 
} 

这一个工作,因为我们没有遍历散列,但在其密钥的存储副本。

+1

为什么写到一个文件,当你可以去 我的@keys = keys%hash1; ? – Penfold 2010-04-03 12:37:32

+0

由于您只是制作了所有密钥的副本,因此会将内存占用加倍。 – 2010-04-13 20:45:05