2010-05-04 26 views
6

En Perl, ¿cómo puedo conseguir esto:¿Cómo puedo combinar varios hashes en un hash en 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' ] } }; 

a esto:

$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' ] } 
]}; 
+0

Necesitamos ver el código que está generando la salida inicial. Más específicamente, necesitamos conocer todas las variables que Data :: Dumper llama '$ VAR1'. –

+1

¿Qué específicamente sobre la sintaxis de la estructura de datos tiene dificultades? ¿Has leído http://perldoc.perl.org/perldsc.html? ¿Has intentado escribir el problema en pseudocódigo? Una vez que tiene un algoritmo, podemos ayudarlo con la sintaxis, pero esos números no tienen ningún significado para nadie más que usted, ya que no conocemos el contexto de su aplicación. – Ether

+1

Su formato de destino no se ve * que * útil. Tiene '999' asignado a una matriz de hashes separados. Y tiene claves adicionales asignadas de la misma manera también. No estoy seguro de que te compre lo que crees que hace. – Axeman

Respuesta

4

creo que esto está más cerca que nadie más ha conseguido:

Esto hace la mayor parte de lo que quiere. No almacené cosas en matrices de hashes singulares , ya que no creo que eso sea útil.

Su situación no es la habitual. Intenté genérico esto hasta cierto punto, pero no fue posible superar la singularidad de este código.

  • En primer lugar, ya que parece que desea contraer todo con el mismo ID en una entidad resultante de la fusión (con excepciones), tiene que descender a través de la estructura tirando de las definiciones de las entidades. Realizar un seguimiento de los niveles, porque los quiere en forma de un árbol.

  • A continuación, ensambla la tabla de ID, fusionando entidades como sea posible. Tenga en cuenta que tenía 995 definido como un conjunto vacío y un nivel como otro. Entonces, dado su resultado , quería sobrescribir la lista vacía con el hash.

  • Después de eso, tenemos que mover la raíz a la estructura del resultado, descendiendo eso en orden para asignar entidades canónicas a los identificadores en cada nivel.

Como dije, no es algo tan regular. Por supuesto, si aún desea una lista de hashes que no son más que pares, es un ejercicio que le queda.

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); 
+0

wow. muchas gracias; perdón por la falta de código o aclaración, estaba tratando de generar un árbol, e intenté Hash :: Merge, pero no pude resolver el problema acuñado-995 de reemplazar el 995 vacío con el 995 no vacío ; esto funciona maravillosamente y realmente aprecio la ayuda! (también probé con los otros y o hizo lo mismo que Hash :: Merge, o realmente eliminó algunas ramas) – Nick

0

I sangría su salida deseada, ya que era difícil de leer, en beneficio de otras personas que quieren responder . Todavía estoy pensando en una respuesta.

$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' ] } 
        ] 
     }; 

No veo el objetivo de todos estos hashes de entrada simple, ¿no sería mejor lo siguiente?

$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' ] 
        } 
     }; 
0

Suponiendo que los datos anteriores es en un dump.txt archivo, puede Eval pieza por pieza.

Actualizado código de abajo

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]); 

Si desea completamente combinación profunda, se puede pasar al final $ final_data a través de este (no probado !!!) de profundidad fusión:

# 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; 
} 
+0

NOTA: supongo que la única fusión ocurre en la tecla topmist. Si no, es un poco más difícil de hacer, aunque no demasiado difícil ... lo dejé como excercize para el lector por ahora :) – DVK

+0

Bueno, parece que también quiere un árbol de todo, con lo que ser claves de "nivel superior" fusionadas con sus claves de segundo nivel coincidentes. – Axeman

+0

@Axeman - OK ... No creo poder analizar ese comentario en absoluto ... necesito dormir más :) ¿Quieres decir que también quiere fusionar teclas de segundo nivel? – DVK

1

Pruebe esta solución recursiva:

# 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; 
} 

M lo odian al contenido de su corazón para lograr el resultado deseado.

Tras una investigación más a fondo, noté que los está fusionando de una manera diferente que el algoritmo anterior. Tal vez solo use esto como un ejemplo, entonces. El mío hace esto:

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

Y cosas similares para las matrices.

+1

Guau, espero que trabajes tan duro para tu empleador como lo haces con extraños al azar en Internet. :) – Ether

+0

@Ether - puede ser que él es solo la cuenta de marionetas de calcetín de Jon Skeet. (y yo soy el bot de Jon Skeet ... antes de preguntar) – DVK

0

Use push y autovigilancia.

de inicio con el asunto delante de costumbre:

#! /usr/bin/perl 

use warnings; 
use strict; 

Lea su entrada de la muestra de la DATA gestor de archivo y crear una estructura de datos similar a la que se descargó:

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

Su entrada tiene dos casos:

  1. Una referencia a una matriz que contiene datos que se fusionarán con ella s primos que tienen la misma "ruta clave".
  2. De lo contrario, es una referencia a un hash que contiene una referencia a una matriz del caso 1 a cierta profundidad, por lo que tiramos de la capa más externa y seguimos excavando.

Tenga en cuenta el uso de $_[0]. La semántica de las subrutinas Perl es tal que los valores en @_ son alias en lugar de copias. Esto nos permite llamar al merge directamente sin tener que crear primero un grupo de andamios para contener los contenidos fusionados. El código se romperá si copia el valor en su lugar.

sub merge { 
    my $data = shift; 

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

Ahora caminamos @hashes y de manera progresiva fusión de sus contenidos en %merged.

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

No sabemos en qué orden los valores llegado, por lo que realizará una pasada final de limpieza para ordenar las matrices:

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; 

El módulo Data :: Dumper es grande para la depuración rápida!

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

Coloque una copia de la entrada de su pregunta en el DATA gestor de archivo especial:

__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' ] } }; 

Una muestra de la salida es a continuación:

 '994' => { 
    '910' => [], 
    '985' => [], 
    '983' => [] 
    }, 
    '999' => { 
    '993' => [], 
    '992' => [ 
     '905' 
    ], 
    '997' => [ 
     '984', 
     '986', 
     '987', 
     '988', 
     '989', 
     '990' 
    ],
2

Uso CPAN! Trate Hash::Merge

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

Ver CPAN para obtener más información, lo hace casi todo lo que se quiere, y es totalmente personalizable.

0

wow. muchas gracias a todos (especialmente a Axeman)!perdón por la falta de código o aclaración, estaba tratando de generar un árbol, e intenté Hash :: Merge, pero no pude resolver el problema acuñado-995 de reemplazar el 995 vacío con el 995 no vacío ; La solución de Axeman funciona muy bien y realmente aprecio la ayuda/colaboración. (También probé con los otros y o hizo lo mismo que Hash :: Merge, o realmente eliminó algunas ramas).

algunos antecedentes en la entrada: tenían un conjunto de valores hash, cada uno tenía claves (todo el mismo nivel) y dos definían a) un padre a otro, yb) sí mismo (el resto eran hijos), y así con un árbol, pensé que un hash era perfecto, se me ocurrió un nuevo hash {a} -> {b} -> [c], y aquí estamos ...

otra vez, gracias a todos y Axeman!

Cuestiones relacionadas