Обход многомерного хэша в Perl - PullRequest
5 голосов
/ 02 октября 2008

Если у вас есть хеш (или ссылка на хеш) в perl со многими измерениями, и вы хотите перебрать все значения, каков лучший способ сделать это. Другими словами, если у нас есть $ f -> {$ x} {$ y}, я хочу что-то вроде

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

вместо

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

Ответы [ 8 ]

12 голосов
/ 02 октября 2008

Первый этап: не изобретать велосипед:)

Быстрый поиск в CPAN подбрасывает невероятно полезные Data :: Walk . Определите подпрограмму для обработки каждого узла, и вы отсортированы

use Data::Walk;

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

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

walk \&process, $data;

А Боб твой дядя. Обратите внимание, что если вы хотите передать ему хеш-код, вам нужно будет передать на него ссылку (см. perldoc perlref ) следующим образом (в противном случае он также попытается обработать ваши хэш-ключи). !):

walk \&process, \%hash;

Для более комплексного решения (но на первый взгляд труднее найти его в CPAN), используйте Data :: Visitor :: Callback или его родительский модуль - это дает вам преимущество в более точном управлении тем, что вы делаете, и (только для дополнительного уличного кредита) написано с помощью лося.

11 голосов
/ 02 октября 2008

Вот вариант. Это работает для произвольно глубоких хэшей:

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";
});
6 голосов
/ 02 октября 2008

Для меня это звучит так, как будто Data :: Diver или Data :: Visitor - это хорошие подходы для вас.

2 голосов
/ 02 октября 2008

Вы также можете пометить многомерные массивы, если у вас всегда есть все значения ключей, или вам просто не нужно обращаться к отдельным уровням как отдельным массивам:

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

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

При этом используется индекс-разделитель "$;" в качестве разделителя для нескольких значений в ключе.

2 голосов
/ 02 октября 2008

Имейте в виду, что списки Perl и хэши не имеют размеров и поэтому не могут быть многомерными. То, что вы можете можете иметь, это элемент хеша, который настроен для ссылки на другой хеш или список. Это может быть использовано для создания поддельных многомерных структур.

Как только вы поймете это, все станет легко. Например:

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

Добавить все, что нужно сделать, кроме обхода структуры, конечно.

Один изящный способ сделать то, что вам нужно, это передать ссылку на код, которая будет вызываться изнутри f. Используя субпрототипирование, вы могли бы даже сделать вызовы похожими на функции Perl grep и map.

1 голос
/ 02 октября 2008

Нет способа получить семантику, которую вы описываете, потому что foreach перебирает список по одному элементу за раз. Вместо этого вам нужно будет deep_keys вернуть LoL (список списков). Даже это не работает в общем случае произвольной структуры данных. Могут быть разные уровни вложенных хешей, некоторые из них могут быть ссылками ARRAY и т. Д.

Способ Perlish для этого состоит в том, чтобы написать функцию, которая может обходить произвольную структуру данных и применять обратный вызов на каждом «листе» (то есть не ссылочном значении). bmdhacks 'ответ является отправной точкой. Точная функция будет зависеть от того, что вы хотите сделать на каждом уровне. Это довольно просто, если все, что вас волнует, это значения листьев. Все усложняется, если вы заботитесь о ключах, индексах и т. Д., Которые привели вас к листу.

1 голос
/ 02 октября 2008

Если вы работаете с древовидными данными глубиной более двух уровней, и вам захочется пройтись по этому дереву, вам следует сначала подумать о том, что вы собираетесь проделать большую дополнительную работу для себя, если планируете переопределить все вам нужно делать вручную хэши хэшей, когда есть много хороших доступных альтернатив ( поиск CPAN для "Tree" ).

Не зная, каковы ваши требования к данным на самом деле, я буду слепо указывать вам на учебник для Tree :: DAG_Node , чтобы вы начали.

Тем не менее, Аксеман прав, мошенничество легче всего сделать с помощью рекурсии. Вот пример для начала, если вы чувствуете, что должны решить свою проблему с помощью хэшей хэшей:

#!/usr/bin/perl
use strict;
use warnings;

my %hash = (
    "toplevel-1" => 
    { 
        "sublevel1a"  => "value-1a",
        "sublevel1b"  => "value-1b"
    },
    "toplevel-2" =>
    {
        "sublevel1c" => 
        {
            "value-1c.1" => "replacement-1c.1",
            "value-1c.2" => "replacement-1c.2"
        },
        "sublevel1d" => "value-1d"
    }
);

hashwalk( \%hash );

sub hashwalk
{
    my ($element) = @_;
    if( ref($element) =~ /HASH/ )
    {
        foreach my $key (keys %$element)
        {
            print $key," => \n";
            hashwalk($$element{$key});
        }
    }
    else
    {
        print $element,"\n";
    }
}

Будет выведено:

toplevel-2 => 
sublevel1d => 
value-1d
sublevel1c => 
value-1c.2 => 
replacement-1c.2
value-1c.1 => 
replacement-1c.1
toplevel-1 => 
sublevel1a => 
value-1a
sublevel1b => 
value-1b

Обратите внимание, что вы НЕ МОЖЕТЕ предсказать, в каком порядке будут проходить хеш-элементы, если вы не свяжете хеш через Tie :: IxHash или аналогичный - опять же, если вы собираетесь выполнить такую ​​большую работу, я рекомендую модуль дерева .

1 голос
/ 02 октября 2008

Достаточно просто, если все, что вы хотите сделать, это работать со значениями, но если вы хотите работать с ключами, вам нужны спецификации того, как уровни могут быть восстановлены.

а. Например, вы можете указать ключи как "$level1_key.$level2_key.$level3_key" - или любой разделитель, представляющий уровни.

б. Или вы могли бы иметь список ключей.

Я рекомендую последнее.

  • Уровень может быть понят как @$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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...