5

Perl に多くの次元を持つハッシュ (またはハッシュへの参照) があり、すべての値を反復処理したい場合、どのように行うのが最善の方法でしょうか。つまり、$f->{$x}{$y} がある場合、次のようなものが必要です。

foreach ($x, $y) (deep_keys %{$f})
{
}

それ以外の

foreach $x (keys %f) 
    {
    foreach $y (keys %{$f->{$x}) 
    {
    }
}
4

8 に答える 8

12

ステージ 1: 車輪を再発明しないでください :)

CPAN で簡単に検索すると、信じられないほど便利なData::Walkが表示されます。各ノードを処理するサブルーチンを定義すると、ソートされます

use Data::Walk;

my $data = { # some complex hash/array mess };

sub process {
   print "current node $_\n";
}

walk \&process, $data;

ボブはあなたのおじです。walk にハッシュを渡したい場合は、次のように参照を渡す必要があることに注意してください ( perldoc perlrefを参照) (そうしないと、ハッシュ キーも同様に処理しようとします!):

walk \&process, \%hash;

より包括的なソリューション (ただし、CPAN で一見すると見つけにくい) については、Data::Visitor::Callbackまたはその親モジュールを使用します。これには、実行することをより細かく制御できるという利点があります。 cred) は Moose を使用して書かれています。

于 2008-10-02T08:36:49.907 に答える
11

ここにオプションがあります。これは、任意の深いハッシュに対して機能します。

sub deep_keys_foreach
{
    my ($hashref, $code, $args) = @_;

    while (my ($k, $v) = each(%$hashref)) {
        my @newargs = defined($args) ? @$args : ();
        push(@newargs, $k);
        if (ref($v) eq 'HASH') {
            deep_keys_foreach($v, $code, \@newargs);
        }
        else {
            $code->(@newargs);
        }
    }
}

deep_keys_foreach($f, sub {
    my ($k1, $k2) = @_;
    print "inside deep_keys, k1=$k1, k2=$k2\n";
});
于 2008-10-02T00:07:11.783 に答える
6

これは、Data::DiverまたはData::Visitorが適切なアプローチであるかのように思えます。

于 2008-10-02T08:43:08.470 に答える
2

Perl のリストとハッシュには次元がないため、多次元にすることはできないことに注意してください。あなた持つことができるのは、別のハッシュまたはリストを参照するように設定されたハッシュ項目です。これは、偽の多次元構造を作成するために使用できます。

これに気づくと、物事は簡単になります。例えば:

sub f($) {
  my $x = shift;
  if( ref $x eq 'HASH' ) {
    foreach( values %$x ) {
      f($_);
    }
  } elsif( ref $x eq 'ARRAY' ) {
    foreach( @$x ) {
      f($_);
    }
  }
}

もちろん、構造をトラバースする以外に必要なことは何でも追加します。

必要なことを行うための気の利いた方法の 1 つは、f 内から呼び出されるコード参照を渡すことです。サブプロトタイピングを使用することで、呼び出しを Perl の grep および map 関数のように見せることさえできます。

于 2008-10-01T23:44:35.333 に答える
2

常にすべてのキー値を持っている場合、または個々のレベルに個別の配列としてアクセスする必要がない場合は、多次元配列をごまかすこともできます。

$arr{"foo",1} = "one";
$arr{"bar",2} = "two";

while(($key, $value) = each(%arr))
{
    @keyValues = split($;, $key);
    print "key = [", join(",", @keyValues), "] : value = [", $value, "]\n";
}

これは、添え字の区切り記号「$;」を使用します。キー内の複数の値の区切りとして。

于 2008-10-02T04:31:11.433 に答える
1

値を操作したいだけなら簡単ですが、キーを操作したい場合は、レベルが回復可能になる方法を指定する必要があります。

を。たとえば、キーを"$level1_key.$level2_key.$level3_key"-- またはレベルを表す任意の区切り記号として指定できます。

b. または、キーのリストを取得できます。

私は後者をお勧めします。

  • でわかるレベル@$key_stack

  • 最もローカルなキーは$key_stack->[-1].

  • パスは次の方法で再構築できます。join( '.', @$key\_stack )

コード:

use constant EMPTY_ARRAY => [];
use strict;    
use Scalar::Util qw<reftype>;

sub deep_keys (\%) { 
    sub deeper_keys { 
        my ( $key_ref, $hash_ref ) = @_;
        return [ $key_ref, $hash_ref ] if reftype( $hash_ref ) ne 'HASH';
        my @results;

        while ( my ( $key, $value ) = each %$hash_ref ) { 
            my $k = [ @{ $key_ref || EMPTY_ARRAY }, $key ];
            push @results, deeper_keys( $k, $value );
        }
        return @results;
    }

    return deeper_keys( undef, shift );
}

foreach my $kv_pair ( deep_keys %$f ) { 
    my ( $key_stack, $value ) = @_;
    ...
}

これは Perl 5.10 でテストされています。

于 2008-10-01T23:24:05.227 に答える
1

foreach一度に 1 要素ずつリストを反復処理するため、記述したセマンティクスを取得する方法はありません。deep_keys代わりに LoL (リストのリスト) を返す必要があります。それでも、任意のデータ構造の一般的なケースでは機能しません。さまざまなレベルのサブハッシュが存在する可能性があり、レベルの一部は ARRAY 参照などである可能性があります。

これを行う Perlish の方法は、任意のデータ構造をウォークし、各「リーフ」(つまり、非参照値) でコールバックを適用できる関数を作成することです。bmdhacks の答えは出発点です。正確な機能は、各レベルで何をしたいかによって異なります。気にするのが葉の値だけであれば、それは非常に簡単です。リーフにたどり着いたキーやインデックスなどに関心があると、事態はさらに複雑になります。

于 2008-10-02T01:43:25.250 に答える
1

2 レベル以上の深さのツリー データを扱っていて、そのツリーをたどりたいと思う場合は、必要なものすべてを再実装することを計画している場合、自分で多くの余分な作業を行うことになることを最初に考慮する必要があります。利用可能な優れた代替手段がたくさんある場合は、ハッシュのハッシュのハッシュで手動で行います(CPANで「ツリー」を検索してください)。

実際のデータ要件が何であるかがわからないので、始められるように Tree::DAG_Node のチュートリアルをやみくもに紹介します。

そうは言っても、Axemanは正しいです。ハッシュウォークは再帰で最も簡単に実行できます。ハッシュのハッシュのハッシュで問題を絶対に解決しなければならないと感じた場合に、開始するための例を次に示します。

#!/usr/bin/perl
厳密に使用します。
警告を使用します。

私の%hash = (
    "トップレベル-1" =>
    {
        "sublevel1a" => "value-1a",
        "sublevel1b" => "値-1b"
    }、
    "トップレベル-2" =>
    {
        "sublevel1c" =>
        {
            "value-1c.1" => "replacement-1c.1",
            「値-1c.2」=>「置換-1c.2」
        }、
        "sublevel1d" => "値-1d"
    }
);

ハッシュウォーク(\%ハッシュ);

サブハッシュウォーク
{
    私の($要素)= @_;
    if( ref($要素) =~ /HASH/ )
    {
        foreach my $key (keys %$element)
        {
            print $key," => \n";
            ハッシュウォーク($$要素{$キー});
        }
    }
    そうしないと
    {
        print $element,"\n";
    }
}

次のように出力されます。

トップレベル-2 =>
sublevel1d =>
値-1d
sublevel1c =>
値-1c.2 =>
交換-1c.2
値-1c.1 =>
交換-1c.1
トップレベル-1 =>
サブレベル1a =>
値-1a
サブレベル1b =>
値-1b

Tie::IxHash などを介してハッシュを結び付けない限り、ハッシュ要素がどの順序でトラバースされるかを予測できないことに注意してください。繰り返しますが、それほど多くの作業を行う場合は、ツリー モジュールをお勧めします。

于 2008-10-02T00:08:24.467 に答える