2010-04-02 77 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