Как я могу объединить несколько хэшей в один хэш в Perl?

В Perl, как мне получить это:

$VAR1 = { '999' => { '998' => [ '908', '906', '0', '998', '907' ] } }; 
$VAR1 = { '999' => { '991' => [ '913', '920', '918', '998', '916', '919', '917', '915', '912', '914' ] } }; 
$VAR1 = { '999' => { '996' => [] } }; 
$VAR1 = { '999' => { '995' => [] } }; 
$VAR1 = { '999' => { '994' => [] } }; 
$VAR1 = { '999' => { '993' => [] } }; 
$VAR1 = { '999' => { '997' => [ '986', '987', '990', '984', '989', '988' ] } }; 
$VAR1 = { '995' => { '101' => [] } }; 
$VAR1 = { '995' => { '102' => [] } }; 
$VAR1 = { '995' => { '103' => [] } }; 
$VAR1 = { '995' => { '104' => [] } }; 
$VAR1 = { '995' => { '105' => [] } }; 
$VAR1 = { '995' => { '106' => [] } }; 
$VAR1 = { '995' => { '107' => [] } }; 
$VAR1 = { '994' => { '910' => [] } }; 
$VAR1 = { '993' => { '909' => [] } }; 
$VAR1 = { '993' => { '904' => [] } }; 
$VAR1 = { '994' => { '985' => [] } }; 
$VAR1 = { '994' => { '983' => [] } }; 
$VAR1 = { '993' => { '902' => [] } }; 
$VAR1 = { '999' => { '992' => [ '905' ] } }; 

к этому:

$VAR1 = { '999:' => [
 { '992' => [ '905' ] },
 { '993' => [
  { '909' => [] },
  { '904' => [] },
  { '902' => [] }
 ] },
 { '994' => [
  { '910' => [] },
  { '985' => [] },
  { '983' => [] }
 ] },
 { '995' => [
  { '101' => [] },
  { '102' => [] },
  { '103' => [] },
  { '104' => [] },
  { '105' => [] },
  { '106' => [] },
  { '107' => [] }
 ] },
 { '996' => [] },
 { '997' => [ '986', '987', '990', '984', '989', '988' ] },
 { '998' => [ '908', '906', '0', '998', '907' ] },
 { '991' => [ '913', '920', '918', '998', '916', '919', '917', '915', '912', '914' ] }
]};

person Nick    schedule 04.05.2010    source источник
comment
Нам нужно увидеть код, который генерирует исходный вывод. Точнее, нам нужно знать все переменные, которые Data::Dumper вызывает $VAR1.   -  person Michael Carman    schedule 04.05.2010
comment
Что конкретно в синтаксисе структуры данных вызывает у вас затруднения? Вы читали perldoc.perl.org/perldsc.html? Вы пробовали написать проблему в псевдокоде? Если у вас есть алгоритм, мы можем помочь вам с синтаксисом, но эти числа не имеют значения ни для кого, кроме вас, поскольку мы не знаем контекста вашего приложения.   -  person Ether    schedule 04.05.2010
comment
Ваш целевой формат не выглядит настолько полезным. У вас есть «999», сопоставленный с массивом отдельных хэшей. И у вас есть дополнительные клавиши, сопоставленные таким же образом. Я не уверен, что это покупает вам то, что вы могли бы подумать.   -  person Axeman    schedule 04.05.2010


Ответы (7)


Я думаю, что это ближе, чем кто-либо другой:

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

Ваш сценарий не обычный. Я пытался до некоторой степени обобщить это, но мне не удалось преодолеть сингулярность этого кода.

  • Прежде всего, потому что кажется, что вы хотите свернуть все с одним и тем же идентификатором в объединенную сущность (с исключениями), вам нужно пройти через структуру, вытягивая определения сущностей. Отслеживание уровней, потому что вы хотите, чтобы они были в виде дерева.

  • Затем вы собираете таблицу идентификаторов, объединяя сущности, насколько это возможно. Обратите внимание, что вы определили 995 как пустой массив в одном месте и как уровень в другом. Итак, учитывая ваш вывод, я хотел перезаписать пустой список хешем.

  • После этого нам нужно переместить корень в структуру результата, спускаясь по ней, чтобы присвоить идентификаторам канонические сущности на каждом уровне.

Как я уже сказал, это не что-то такое обычное. Конечно, если вам по-прежнему нужен список хэшей, состоящих не более чем из пар, это упражнение остается за вами.

use strict;
use warnings;

# subroutine to identify all elements
sub descend_identify {
    my ( $level, $hash_ref ) = @_;
    # return an expanding list that gets populated as we desecend 
    return map {
        my $item = $hash_ref->{$_};
        $_ => ( $level, $item )
            , ( ref( $item ) eq 'HASH' ? descend_identify( $level + 1, $item ) 
              :                          ()
              )
           ;
    } keys %$hash_ref
    ;
}

# subroutine to refit all nested elements
sub descend_restore { 
    my ( $hash, $ident_hash ) = @_;

    my @keys        = keys %$hash;
    @$hash{ @keys } = @$ident_hash{ @keys };
    foreach my $h ( grep { ref() eq 'HASH' } values %$hash ) {
        descend_restore( $h, $ident_hash );
    }
    return;
}

# merge hashes, descending down the hash structures.
sub merge_hashes {
    my ( $dest_hash, $src_hash ) = @_;
    foreach my $key ( keys %$src_hash ) {
        if ( exists $dest_hash->{$key} ) {
            my $ref = $dest_hash->{$key};
            my $typ = ref( $ref );
            if ( $typ eq 'HASH' ) {
                merge_hashes( $ref, $src_hash->{$key} );
            }
            else { 
                push @$ref, $src_hash->{$key};
            }
        }
        else {
            $dest_hash->{$key} = $src_hash->{$key};
        }
    }
    return;
}

my ( %levels, %ident_map, %result );

#descend through every level of hash in the list
# @hash_list is assumed to be whatever you Dumper-ed.
my @pairs = map { descend_identify( 0, $_ ); } @hash_list;

while ( @pairs ) {
    my ( $key, $level, $ref ) = splice( @pairs, 0, 3 );
    $levels{$key} |= $level;

    # if we already have an identity for this key, merge the two
    if ( exists $ident_map{$key} ) {
        my $oref = $ident_map{$key};
        my $otyp = ref( $oref );
        if ( $otyp ne ref( $ref )) {
            # empty arrays can be overwritten by hashrefs -- per 995
            if ( $otyp eq 'ARRAY' && @$oref == 0 && ref( $ref ) eq 'HASH' ) {
                $ident_map{$key} = $ref;
            }
            else { 
                die "Uncertain merge for '$key'!";
            }
        }
        elsif ( $otyp eq 'HASH' ) {
            merge_hashes( $oref, $ref );
        }
        else {
            @$oref = sort { $a <=> $b || $a cmp $b } keys %{{ @$ref, @$oref }};
        }
    }
    else {
        $ident_map{$key} = $ref;
    }
}

# Copy only the keys that do not appear at higher levels to the 
# result hash
if ( my @keys = grep { !$levels{$_} } keys %ident_map ) { 
    @result{ @keys } = @ident_map{ @keys } if @keys;

}
# then step through the hash to make sure that the entries at
# all levels are equal to the identity
descend_restore( \%result, \%ident_map );
person Axeman    schedule 04.05.2010
comment
ух ты. Спасибо; извините за отсутствие кода или пояснений, я пытался сгенерировать дерево и пробовал Hash::Merge, но не смог на всю жизнь решить проблему с придуманным-995 заменой пустого 995 непустым 995 ; это прекрасно работает, и я очень ценю помощь! (также попробовал другие, и он либо сделал то же самое, что и Hash::Merge, либо фактически избавился от некоторых ветвей) - person Nick; 05.05.2010

Используйте CPAN! Попробуйте Hash::Merge

# OO interface.  
my $merge = Hash::Merge->new( 'LEFT_PRECEDENT' );
my %c = %{ $merge->merge( \%a, \%b ) };

См. CPAN для получения дополнительной информации, он делает практически все, что вам нужно, и полностью настраивается.

person Evan Carroll    schedule 04.05.2010

Попробуйте это рекурсивное решение:

#   XXX: doesn't handle circular problems...
sub deepmerge {
    my (@structs) = @_;
    my $new;

    # filter out non-existant structs
    @structs = grep {defined($_)} @structs;

    my $ref = ref($structs[0]);
    if (not all(map {ref($_) eq $ref} @structs)) { 
        warn("deepmerge: all structs are not $ref\n");
    } 

    my @tomerge = grep {ref($_) eq $ref} @structs;
    return qr/$tomerge[0]/ if scalar(@tomerge) == 1 and $ref eq 'Regexp';
    return $tomerge[0] if scalar(@tomerge) == 1;

    if ($ref eq '') { 
        $new = pop(@tomerge); # prefer farthest right
    } 
    elsif ($ref eq 'Regexp') { 
        $new = qr/$tomerge[$#tomerge]/;
    } 
    elsif ($ref eq 'ARRAY') { 
        $new = [];
        for my $i (0 .. max(map {scalar(@$_) - 1} @tomerge)) { 
            $new->[$i] = deepmerge(map {$_->[$i]} @tomerge);
        }
    } 
    elsif ($ref eq 'HASH') { 
        $new = {};
        for my $key (uniq(map {keys %$_} @tomerge)) { 
            $new->{$key} = deepmerge(map {$_->{$key}} @tomerge);
        }
    }
    else {
        # ignore all other structures...
        $new = '';
    }

    return $new;
}

Измените его на свое усмотрение, чтобы добиться желаемого результата.

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

deepmerge({k => 'v'}, {k2 => 'v2'});
# returns {k => 'v', k2 => 'v2'}

И подобные вещи для массивов.

person dlamotte    schedule 04.05.2010
comment
Вау, я надеюсь, что вы работаете так же усердно для своего работодателя, как и для случайных незнакомцев в Интернете. :) - person Ether; 04.05.2010
comment
@Ether - может быть, он просто кукольный аккаунт Джона Скита. (и я всего лишь бот Джона Скита... пока вы не спросили) - person DVK; 04.05.2010

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

$VAR1 = { '999:' => [
                      { '992' => [ '905' ] },
                      { '993' => [
                                   { '909' => [] },
                                   { '904' => [] },
                                   { '902' => [] }
                                 ]
                      },
                      { '994' => [
                                   { '910' => [] },
                                   { '985' => [] },
                                   { '983' => [] }
                                 ]
                      },
                      { '995' => [
                                   { '101' => [] },
                                   { '102' => [] },
                                   { '103' => [] },
                                   { '104' => [] },
                                   { '105' => [] },
                                   { '106' => [] },
                                   { '107' => [] }
                                 ]
                      },
                      { '996' => [] },
                      { '997' => [ '986', '987', '990', '984', '989', '988' ] },
                      { '998' => [ '908', '906', '0', '998', '907' ] },
                      { '991' => [ '913', '920', '918', '998', '916', '919', '917', '915', '912', '914' ] }
                    ]
        };

Я не вижу смысла во всех этих хэшах с одной записью, не лучше ли будет следующее?

$VAR1 = { '999:' => {
                      '992' => [ '905' ],
                      '993' => {
                                 '909' => [],
                                 '904' => [],
                                 '902' => []
                               },
                      '994' => {
                                 '910' => [],
                                 '985' => [],
                                 '983' => []
                               },
                      '995' => {
                                 '101' => [],
                                 '102' => [],
                                 '103' => [],
                                 '104' => [],
                                 '105' => [],
                                 '106' => [],
                                 '107' => []
                               },
                      '996' => [],
                      '997' => [ '986', '987', '990', '984', '989', '988' ],
                      '998' => [ '908', '906', '0', '998', '907' ],
                      '991' => [ '913', '920', '918', '998', '916', '919', '917', '915', '912', '914' ]
                    }
        };
person Christoffer Hammarström    schedule 04.05.2010

Предполагая, что приведенные выше данные находятся в файле dump.txt, вы можете оценить его по частям.

Обновленный код ниже

use strict;
use File::Slurp;
my $final_data = {}; 
my @data = map {eval $_} (read_file("dump.txt") =~ /\$VAR1 = ([^;]+);/gs);
foreach my $element (@data) {
    my $key = (keys %$element)[0]; 
    $final_data->{$key} ||= []; 
    push @{$final_data->{$key}}, $element->{$key}
}; 
use Data::Dumper; 
print Data::Dumper->Dump([$final_data]);

Если вы хотите полностью выполнить глубокое слияние, вы можете в конце передать $final_data через это (не проверенное!!!) глубокое слияние:

# Merge an array of hashes as follows:
# IN:  [ { 1 => 11 }, { 1 => 12 },{ 2 => 22 } ]
# OUT: { 1 => [ 11, 12 ], 2 => [ 22 ] }
# This is recursive - if array [11,12] was an array of hashrefs, we merge those too
sub merge_hashes {
    my $hashes = @_[0];
    return $hashes unless ref $hashes eq ref []; # Hat tip to brian d foy
    return $hashes unless grep { ref @_ eq ref {} } @$hashes; # Only merge array of hashes
    my $final_hashref = {};
    foreach my $element (@$hashes) {
        foreach my $key (keys %$element) {
            $final_hashref->{$key} ||= [];
            push @{ $final_hashref->{$key} }, $element->{$key};
        }
    }
    foreach my $key (keys %$final_hashref) {
        $final_hashref->{$key} = merge_hashes($final_hashref->{$key});
    }
    return $final_hashref;
}
person DVK    schedule 04.05.2010
comment
ПРИМЕЧАНИЕ. Я предполагаю, что слияние происходит только в ключе topmist. Если нет, то это немного сложнее сделать, хотя и не слишком сложно... пока оставим читателю в качестве упражнения :) - person DVK; 04.05.2010
comment
Ну, это выглядит так, как будто он также хочет дерево из всего этого, с тем, что было бы ключами верхнего уровня, объединенными с соответствующими ключами второго уровня. - person Axeman; 04.05.2010
comment
@Axeman - Хорошо ... Я не думаю, что вообще смогу разобрать этот комментарий ... нужно больше спать :) Вы имеете в виду, что он также хочет объединить ключи второго уровня? - person DVK; 04.05.2010
comment
Обновлен мой исходный код, чтобы учесть тот факт, что в реальной жизни выходные данные Data::Dumper не обязательно будут хорошо выстроены по 1 строке на вызов :) - person DVK; 04.05.2010
comment
@DVK: Извините, сейчас я часто использую сокращенные глаголы. Если вы возьмете желаемый результат в текстовый редактор и проверите соответствие фигурным скобкам, вы увидите, что единственным ключом верхнего уровня является «999», поэтому он служит корнем древовидной структуры. - person Axeman; 04.05.2010

Используйте push и автоживификацию.

Начните с обычного переднего вопроса:

#! /usr/bin/perl

use warnings;
use strict;

Прочитайте ваш пример ввода из дескриптора файла DATA и создайте структуру данных, подобную той, которую вы выгрузили:

my @hashes;
while (<DATA>) {
  my $VAR1;
  $VAR1 = eval $_;
  die $@ if $@;
  push @hashes => $VAR1;
}

Ваш ввод имеет два случая:

  1. Ссылка на массив, содержащий данные, которые нужно объединить с его двоюродными братьями, имеющими тот же «ключевой путь».
  2. В противном случае это ссылка на хэш, который содержит ссылку на массив из случая 1 на некоторой глубине, поэтому мы срезаем самый внешний слой и продолжаем копать.

Обратите внимание на использование $_[0]. Семантика подпрограмм Perl такова, что значения в @_ являются псевдонимами, а не копиями. Это позволяет нам вызывать merge напрямую, без необходимости сначала создавать множество каркасов для хранения объединенного содержимого. Код сломается, если вместо этого вы скопируете значение.

sub merge {
  my $data = shift;

  if (ref($data) eq "ARRAY") {
    push @{ $_[0] } => @$data;
  }
  else {
    foreach my $k (%$data) {
      merge($data->{$k} => $_[0]{$k});
    }
  }
}

Теперь проходим @hashes и постепенно объединяем их содержимое в %merged.

my %merged;    
foreach my $h (@hashes) {
  foreach my $k (keys %$h) {
    merge $h->{$k} => $merged{$k};
  }
}

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

sub sort_arrays {
  my($root) = @_;
  if (ref($root) eq "ARRAY") {
    @$root = sort { $a <=> $b } @$root;
  }
  else {
    sort_arrays($root->{$_}) for keys %$root;
  }
}

sort_arrays \%merged;

Модуль Data::Dumper отлично подходит для быстрой отладки!

use Data::Dumper;
$Data::Dumper::Indent = 1;
print Dumper \%merged;

Поместите копию ввода из вашего вопроса в специальный дескриптор файла DATA:

__DATA__
$VAR1 = { '999' => { '998' => [ '908', '906', '0', '998', '907' ] } };
$VAR1 = { '999' => { '991' => [ '913', '920', '918', '998', '916', '919', '917', '915', '912', '914' ] } };
$VAR1 = { '999' => { '996' => [] } };
$VAR1 = { '999' => { '995' => [] } };
$VAR1 = { '999' => { '994' => [] } };
$VAR1 = { '999' => { '993' => [] } };
$VAR1 = { '999' => { '997' => [ '986', '987', '990', '984', '989', '988' ] } };
$VAR1 = { '995' => { '101' => [] } };
$VAR1 = { '995' => { '102' => [] } };
$VAR1 = { '995' => { '103' => [] } };
$VAR1 = { '995' => { '104' => [] } };
$VAR1 = { '995' => { '105' => [] } };
$VAR1 = { '995' => { '106' => [] } };
$VAR1 = { '995' => { '107' => [] } };
$VAR1 = { '994' => { '910' => [] } };
$VAR1 = { '993' => { '909' => [] } };
$VAR1 = { '993' => { '904' => [] } };
$VAR1 = { '994' => { '985' => [] } };
$VAR1 = { '994' => { '983' => [] } };
$VAR1 = { '993' => { '902' => [] } };
$VAR1 = { '999' => { '992' => [ '905' ] } };

Пример вывода ниже:

  '994' => {
    '910' => [],
    '985' => [],
    '983' => []
  },
  '999' => {
    '993' => [],
    '992' => [
      '905'
    ],
    '997' => [
      '984',
      '986',
      '987',
      '988',
      '989',
      '990'
    ],
person Greg Bacon    schedule 04.05.2010

ух ты. Большое спасибо всем (особенно Axeman)! извините за отсутствие кода или пояснений, я пытался сгенерировать дерево и пробовал Hash::Merge, но не смог на всю жизнь решить проблему с придуманным-995 заменой пустого 995 непустым 995 ; Решение Axeman прекрасно работает, и я очень ценю помощь/сотрудничество! (также попробовал другие, и он либо сделал то же самое, что и Hash::Merge, либо фактически избавился от некоторых ветвей).

некоторая предыстория на входе: имел набор хэшей, у каждого были ключи (все одного уровня) и два из которых определяли а) родителя другому, и б) себя самого (остальные были дочерними), и так с деревом, я решил, что хеш идеален, придумал набор новых хэшей {a}->{b}->[c], и вот мы здесь...

еще раз, спасибо всем и Axeman!

person Nick    schedule 05.05.2010