2016-05-24 6 views
2

Bitte, ich versuche, eine komplexe Datenstruktur zu erstellen. Ich weiß, wieWie erstelle ich rekursiven Hash von Hash? (Mit unbegrenzt tief)

$branch{'level1'}{'level2'}{'level3'}='leaf'; 

zu tun, aber ich weiß nicht, wie erstellen

$branch{'level1'}....{'levelN'}='leaf'; 

ich so etwas versuchen:

$branch{'leaf'} = "1"; 
$branchREF = \%branch; 
$branchtmp{'level3'} = $branchREF; 

Also ich succefully erhalten:

$VAR1 = 'level3'; 
$VAR2 = { 
      'leaf' => '1' 
     }; 

Aber für den nächsten Schritt, einen rekursiven N-Hash von h zu tun Asche, ich versuche:

%branch = %branchtmp; 

Aber das Ergebnis ist völlig falsch ... %branch ist nicht das, was ich erwarte. Um meine Rekursivität zu nutzen, muss ich meine erste %branch wiederverwenden und keine neue erstellen. Wie kann ich bitte machen?

A.

Antwort

2

Die einfachste Weg ist t o Denken Sie daran, wie Perl mehrdimensionale Datenstrukturen behandelt. Dies geschieht anhand von Referenzen. So können Sie finden es einfacher, auf der obersten Ebene zu starten, als Hash-ref statt:

#!/usr/bin/env perl 
use strict; 
use warnings; 
use Data::Dumper; 

my @levels = qw (level1 level2 level3); 

my $branch = {}; 

my $tail = pop(@levels); 
my $cursor = $branch; 
#iterate our levels 
foreach my $level (@levels) { 
    #make a new anon-hash if there isn't one. 
    $cursor -> {$level} ||= {}; 
    #traverse down 
    $cursor = $cursor->{$level}; 
} 
#set a value 
$cursor -> {$tail} = 'leaf'; 

print Dumper $branch; 

Ausgang:

$VAR1 = { 
      'level1' => { 
         'level2' => { 
             'level3' => 'leaf' 
            } 
         } 
     }; 

Hinweis - Sie hätte den Cursor und 'Re-Traverse' zurücksetzen um dies wieder zu tun, könnte aber die Struktur in ähnlicher Weise 'laufen'.

+0

Dies kann sehr vereinfacht werden, wenn Sie sowohl $ cursor = ...; mit '$ cursor = \ (...);' ersetzen und alle anderen Instanzen von '$ cursor' anpassen. Siehe meine Antwort (http://stackoverflow.com/a/37417801/589924). – ikegami

3

Data::Diver können Ihnen helfen:

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

use Data::Diver qw{ DiveVal }; 
use Data::Dumper; 

my %branch; 
DiveVal(\%branch, map "level$_", 1 .. 3) = 'leave'; 
print Dumper \%branch; 

Ausgang:

$VAR1 = { 
      'level1' => { 
         'level2' => { 
             'level3' => 'leave' 
            } 
         } 
     }; 

Oder, wenn Sie es selbst implementieren wollen:

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

use Data::Dumper; 

sub set_value { 
    my ($struct, @list) = @_; 
    if (@list > 2) { 
     set_value($struct->{ $list[0] } = {}, @list[ 1 .. $#list ]); 
    } else { 
     $struct->{ $list[0] } = $list[1]; 
    } 
} 


my %branch; 
set_value(\%branch, map("level$_", 1 .. 3), 'leave'); 
print Dumper \%branch; 
+0

Danke Choroba. Ich habe eine Vorliebe für deine zweite Art, es ohne zusätzliches Modul zu machen, aber ich finde die eballes ein bisschen einfacher. Vielen Dank für Ihre Antwort ! :) – Antoine

+1

@choroba, Sie könnten daran interessiert sein, [diese] (http://stackoverflow.com/a/37417801/589924) zu sehen. – ikegami

2

Eine einfache rekursive Lösung:

#!/usr/bin/env perl 
use strict; 
use warnings; 
use Data::Dumper; 

sub recursiveHash { 
    my ($result, @rest) = @_; 
    return $result unless @rest; 

    my $nextTag = pop @rest; 
    return recursiveHash({$nextTag => $result}, @rest); 
} 

print Dumper(recursiveHash('leaf', 'level1', 'level2', 'level3')); 

Ausgang:

$VAR1 = { 
      'level1' => { 
         'level2' => { 
             'level3' => 'leaf' 
            } 
         } 
     }; 

Das heißt, Subroutinenaufrufe sind eher langsam in Perl. Zum Glück ist die Rekursion hier völlig überflüssig.

sub iterativeHash { 
    my ($result, @rest) = @_; 
    while (@rest) { 
     my $nextTag = pop @rest; 
     $result = { $nextTag => $result }; 
    } 
    return $result; 
} 
+2

Ich bin beeindruckt, wie schnell alle antworteten! Ich mag die Art, wie Sie es beantworten: Ich brauche kein zusätzliches Modul und finde den rekursiven Weg ziemlich elegent. Ich bekomme deine Antwort, aber die von Sobrique und Choroba sind auch sehr nett. Vielen Dank ! :) – Antoine

+1

Leider kann dieser Ansatz nicht verwendet werden, um eine bestehende Struktur wie 'DiveVal ($ root, @keys) = $ leaf;' hinzuzufügen. – ikegami

4

Ich empfehle eine bestehende Lösung wie Data::Diver verwenden.

use Data::Diver qw(DiveVal); 

my @keys = map "level$_", 1 .. 3; 

my $branch = {}; 
DiveVal($branch, map \$_, @keys) = 'leaf'; 
    -or- 
my %branch; 
DiveVal(\%branch, map \$_, @keys) = 'leaf'; 

Offensichtlich ohne zu erfolgen.

sub DiveVal :lvalue { 
    my $p = \shift; 
    $p = \($$p->{$_}) for @_; 
    $$p 
} 

my @keys = map "level$_", 1 .. 3; 

my $branch; 
DiveVal($branch, @keys) = 'leaf'; 
    -or- 
my %branch; 
DiveVal(\%branch, @keys) = 'leaf'; 

Wie meine DiveVal Werke:

Pre-loop:   $p references $branch 
After loop pass 0: $p references $branch->{level1} 
After loop pass 1: $p references $branch->{level1}{level2} 
After loop pass 2: $p references $branch->{level1}{level2}{level3} 
Returned:   $branch->{level1}{level2}{level3} 

Die zusätzliche Dereferenzierungsebene hat viele Vorteile.

  • Es beseitigt die Notwendigkeit, den letzten Schlüssel speziell zu behandeln.
  • Es beseitigt die Notwendigkeit, den Hash zu erstellen, bevor es dereferenziert wird.
  • Es entfernt die Notwendigkeit, dass der Stamm eine Referenz auf einen Hash ist. Stattdessen kann jeder Skalar die Wurzel sein, sogar eine undefinierte.
  • Es erleichtert die Erweiterung DiveVal zur Unterstützung gemischter Array/Hash-Strukturen.
+0

Vielen Dank für diese Antwort, ich habe viel davon gelernt. Ich möchte auch nur auf die [Dokumentation von ': lvalue'] verlinken (http://perldoc.perl.org/perlsub.html#Lvalue-subroutines). Ich brauchte es :) – eballes

+0

@ikegami, wenn Sie der Autor von Data :: Diver sind Ich habe Ihnen eine E-Mail über CPAN, d. H. An Ihre cpan.org E-Mail-Adresse gesendet. Ich bin mir nicht sicher, ob die E-Mail an Ihre echte E-Mail-Adresse weitergeleitet wird. – Bulrush

+0

@Bulrush, ich halte dieses Modul nicht aufrecht. Mein CPAN-Name entspricht meinem SO-Namen. Und ja, CPAN-Adressen sind Mail-Forwarder. – ikegami

1

Ich benutze DBM :: Deep, die Sie wahrscheinlich selbst installieren müssen. Ich brauchte etwas, das mit langen Schlüsselwerten und etwa 100.000 Schlüsseln umgehen konnte. Normale Perl-Hashes (die standardmäßig installierten) behandeln nur eine kombinierte Schlüssel- und Datenlänge von 1008 Bytes.


# LIMITS: 
# - Key size limit: none 
# - Data size limit: limited by OS 
# - Number of keys per hash: millions 
# - Number of nested levels: millions. "Can handle millions of keys and unlimited levels without significant slow-down. " 

# Basic 
use Fcntl; # Required for hashes. 
use DBM::Deep; 
my($db); 
my $fn='file.dbm'; 
$db=DBM::Deep->new($fn) or die "Could not make DBM::Deep in file $fn"; 
$db->{'key1'}="stuff"; 
delete $db->{'key1'}; 
if ($db->exists("key")) 
    { 
    print "Yay\n"; 
    } 
$db->clear(); # Delete all keys. 
@k=keys(%$db); # Get all first level keys. 

# Alternate 
my $fn='file.dbm'; 
tie my %db, 'DBM::Deep', $fn or die "Could not tie hash to $fn"; 
$db{key} = 'value'; 
print $db{key}; 

tied(%db)->put('key' => 'value'); 
print tied(%db)->get('key'); 

# Multi-level hash 
$db->{'key1'}->{'subkey1'}="more stuff"; 
$db->{'wine'}->{'red'}->{'taste'}="good"; 
$db->{'invoice'}->{'16738'}->{'customernum'}=455; 

# get keys or values 
@k=keys(%$db); 
@k=values(%$db); 
@k=keys(%{$dbm->{'A'}}); # Gets subkeys for 'A'