Вопрос

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

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

вместо

foreach $x (keys %f) 
    {
    foreach $y (keys %{$f->{$x}) 
    {
    }
}
Это было полезно?

Решение

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

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

Другие советы

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

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

use Data::Walk;

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

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

walk \&process, $data;

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

walk \&process, \%hash;

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

Для меня это звучит так, как будто Данные::Дайвер или Данные::Посетитель хорошие подходы для вас.

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

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

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.

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

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

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

Это использует сепаратор индекса «$»; как сепаратор для нескольких значений в ключе.

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

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

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

а.Например, вы можете указать ключи как "$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.

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

Не зная, каковы на самом деле ваши требования к данным, я собираюсь вслепую указать вам на руководство по 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 или аналогичный — опять же, если вы собираетесь проделать такую ​​большую работу, я рекомендую модуль дерева.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top