4

2 つのハッシュがあるとします。そのうちの 1 つには、他のハッシュに現れるものを保持するだけでよいデータのセットが含まれています。

例えば

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" }  );

私がしたいのは、できればモジュールなしで、hash2{major} のキー/値として存在しない場合、hash1 のサブハッシュ全体を削除することです。「innerX」に含まれる情報は重要ではなく、単にそのままにしておかなければなりません (サブハッシュを削除しない限り、それは消える可能性があります)。

上記の例では、この操作が実行された後、hash1 は次のようになります。

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

hash1{test1} と hash1{test3} は、hash2 と一致しないため削除します。

これが私が現在試したことですが、うまくいきません。また、ハッシュを削除しようとしているときにハッシュをループしているため、おそらく最も安全なことではありません。しかし、私は大丈夫なはずのそれぞれで削除していますか?

これは私の試みでしたが、perl は次のように不平を言っています。

"strict refs" が使用されている間は、文字列 ("inner1") を HASH ref として使用できません

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;別の条件内と同等ですが、すでに 2 回ネストされています。マトリョーシカ人形を作りたくないので、ステートメント修飾子を使用しましたが、名前が示すように、単一のステートメントを変更します。

そこでPerl のカンマ演算子の出番です:

Binary,はコンマ演算子です。スカラー コンテキストでは、左の引数を評価してその値を破棄し、次に右の引数を評価してその値を返します。これは C のコンマ演算子と同じです。

この場合、左の引数は式$delete = 0で、右の引数はlastです。

条件は不必要にうるさいように見えるかもしれませんが、

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

%hash2(test1/inner1 など) に記載されていないテストをプローブすると、値が未定義の警告が生成されます。使用する

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

%hash2で言及されているテストの「内部名」が string などの false 値の場合、誤って削除されます"0"。はい、existsここで使用するのは不必要にうるさいかもしれませんが、実際のハッシュキーがわからないため、保守的なルートを選択しました.

出力:

$VAR1 = {
  'test2' => {
    'inner2' => {
      'somethingelse' => 'デルタ',
      'more' => 'チャーリー'
    }
  }
};

違反していませんが、 の使用に関連する次の警告に注意して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. の第 1 レベル キーは%hash1で言及する必要があります$hash2{major}
  2. 各第 1 レベルのキーに対応する の値は、$hash2{major}それ自体がそのキーのサブキーである必要があります%hash1

(うわー、めまいがする。英語で複数のプレースホルダー変数が必要です!)

単項プラス in+($_ => $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
# 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

これが機能するのは、基本的に 3 つの独立した段階で必要なもの/不要なもののリストを作成するためです。

  1. keys 呼び出しは、hash1 にあるすべてのキーを 1 ステップで取得します。
  2. grep は、基準に一致するキーのリストを (1 つのステップとして) 生成します。
  3. マップは、必要なキーと値のセットを (1 つのステップとして) 生成します。

このようにして、準備が整うまでプライマリ ハッシュを変更することはありません。%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 に答える
1

これが私のやり方です: (3回目の試行が魅力です)

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 に答える