Как объединить несколько хэшей в один хэш в 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' ] }
]};
7 ответов
Я думаю, что это ближе, чем кто-нибудь еще получил:
это делает большую часть того, что вы хотите. Я не хранил вещи в массивах сингулярных хэши, поскольку я не чувствую, что это полезно.
ваш сценарий не обычный. Я пытался genericize это в какой-то степени, но преодолеть сингулярность этого кода не удалось.
прежде всего, потому что кажется, что вы хотите свернуть все с тем же id в объединенный лица (с исключениями), вы должны спуститься через структуру вытягивание определений сущностей. Отслеживание уровней, потому что вы хочу их в виде дерева.
затем вы собираете таблицу ID, объединяя сущности, насколько это возможно. Заметьте, что вы 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 );
использовать CPAN! Попробуй!--2-->Hash:: Merge
# OO interface.
my $merge = Hash::Merge->new( 'LEFT_PRECEDENT' );
my %c = %{ $merge->merge( \%a, \%b ) };
см. CPAN для получения дополнительной информации, он в значительной степени делает все, что вы хотели бы, и полностью настраивается.
попробуйте это рекурсивное решение:
# 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'}
и аналогичные вещи для массивов.
я отступил ваш желаемый результат, поскольку его было трудно прочитать, в интересах других людей, которые хотят ответить. Я все еще думаю над ответом.
$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' ]
}
};
предполагая, что вышеуказанные данные находятся в дампе файлов.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;
}
использовать push
и autovivification.
начните с обычного переднего вопроса:
#! /usr/bin/perl
use warnings;
use strict;
читать ваш образец вклада DATA
filehandle и создайте структуру данных, подобную той, которую вы сбросили:
my @hashes;
while (<DATA>) {
my $VAR1;
$VAR1 = eval $_;
die $@ if $@;
push @hashes => $VAR1;
}
ваш вход имеет два случая:
- ссылка на массив, содержащий данные для объединения со своими двоюродными братьями, которые имеют тот же " ключевой путь."
- в противном случае, это ссылка на хэш, содержащий ссылка на массив из случая 1 на некоторой глубине, поэтому мы снимаем самый внешний слой и продолжаем копать.
обратите внимание на использование $_[0]
. Семантика подпрограмм Perl такова, что значения в @_
are псевдонимы, а не копии. Это позволяет нам позвонить 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
filehandle:
__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' ],
вау. большое спасибо всем (особенно гитариста)! извините за отсутствие кода или разъяснения, я пытался создать дерево и попробовал Hash::Merge, но не смог за всю жизнь решить придуманную проблему-995 замены пустого 995 непустым 995; решение Axeman работает красиво, и я действительно ценю помощь/сотрудничество! (также попробовал другие, и он либо сделал то же самое, что и Hash::Merge, либо фактически избавился от некоторых ветвей).
некоторые фон на входе: имел набор хэшей, у каждого были ключи (все того же уровня) и два из которых определяли а) родителя другому, и Б) себя (остальные были детьми), и поэтому с деревом я посчитал хэш идеальным, придумал набор новых хэшей {a}->{b}->[c], и вот мы здесь...
еще раз спасибо всем и Axeman!