If you want to build a somewhat more flexible solution, you could traverse the data tree recursively. Consider this example data tree (arbitrary depth):
Example data
my %people = (
memowe => {
NAMES => {
memo => {AGE => 666},
we => {AGE => 667},
},
},
bladepanthera => {
NAMES => {
blade => {AGE => 42},
panthera => {AGE => 17},
},
},
);
From your question I concluded you just want to work on the leaves (AGE
s in this case). So one could write a recursive traverse
subroutine that executes a given subref on all leaves it could possibly find in key-sorted depth-first order. This subref gets the leave itself and a path of hash keys for convenience:
Preparations
sub traverse (&$@) {
my ($do_it, $data, @path) = @_;
# iterate
foreach my $key (sort keys %$data) {
# handle sub-tree
if (ref($data->{$key}) eq 'HASH') {
traverse($do_it, $data->{$key}, @path, $key);
next;
}
# handle leave
$do_it->($data->{$key}, @path, $key);
}
}
I think it's pretty clear how this guy works from the inlined comments. It would be no big change to execute the coderef on all nodes and not the leaves only, if you wanted. Note that I exceptionally added a prototype here for convenience because it's pretty easy to use traverse
with the well-known map
or grep
syntax:
Executing stuff on your data
traverse { say shift . " (@_)" } \%people;
Also note that it works on hash references and we initialized the @path
with an implicit empty list.
Output:
42 (bladepanthera NAMES blade AGE)
17 (bladepanthera NAMES panthera AGE)
666 (memowe NAMES memo AGE)
667 (memowe NAMES we AGE)
The given subroutine (written as a { block }
) could do anything with the given data. For example this more readable push subroutine:
my @flattened_people = ();
traverse {
my ($thing, @path) = @_;
push @flattened_people, { age => $thing, path => \@path };
} \%people;