Reputation: 113
How do we retrieve the values of keys in a Hash of Hashes in Perl?
I tried to use the keys function. I wanted to remove the duplicates and then sort them, which i could
do using the uniq
and sort
functions. Am I missing anything?
#!/usr/bin/perl
use warnings;
use strict;
sub ids {
my ($data) = @_;
my @allID = keys %{$data};
my @unique = uniq @allID;
foreach ( @unique ) {
@allUniqueID = $_;
}
my @result = sort{$a<=>$b}(@allUniqueId);
return @result;
}
my $data = {
'first' => {
'second' => {
'third1' => [
{ id => 44, name => 'a', value => 'aa' },
{ id => 48, name => 'b', value => 'bb' },
{ id => 100, name => 'c', value => 'cc' }
],
id => 19
},
'third2' => [
{ id => 199, data => 'dd' },
{ id => 40, data => 'ee' },
{ id => 100, data => { name => 'f', value => 'ff' } }
],
id => 55
},
id => 1
};
# should print “1, 19, 40, 44, 48, 55, 100, 199”
print join(', ', ids($data)) . "\n";
I know it's incomplete, but I am not sure how to proceed. Any help would be appreciated.
Upvotes: 1
Views: 1245
Reputation: 29772
This routine will recursively walk the data structure and pull out all of the values that correspond to a hash key id
, without sorting the results or eliminating duplicates:
sub all_keys {
my $obj = shift;
if (ref $obj eq 'HASH') {
return map {
my $value = $obj->{$_};
$_ eq 'id' ? $value : ref $value ? all_keys($value) : ();
} keys %$obj;
} elsif (ref $obj eq 'ARRAY') {
return map all_keys($_), @$obj;
} else {
return;
}
}
To do the sorting/eliminating, just call it like:
my @ids = sort { $a <=> $b } uniq(all_ids($data));
(I assume the uniq
routine is defined elsewhere.)
Upvotes: 2
Reputation: 126722
Here's my version of the recursive approach
use warnings;
use strict;
sub ids {
my ($data) = @_;
my @retval;
if (ref $data eq 'HASH') {
push @retval, $data->{id} if exists $data->{id};
push @retval, ids($_) for values %$data;
}
elsif (ref $data eq 'ARRAY') {
push @retval, ids($_) for @$data;
}
@retval;
}
my $data = {
'first' => {
'second' => {
'third1' => [
{ id => 44, name => 'a', value => 'aa' },
{ id => 48, name => 'b', value => 'bb' },
{ id => 100, name => 'c', value => 'cc' }
],
id => 19
},
'third2' => [
{ id => 199, data => 'dd' },
{ id => 40, data => 'ee' },
{ id => 100, data => { name => 'f', value => 'ff' } }
],
id => 55
},
id => 1
};
my @ids = sort { $a <=> $b } ids($data);
print join(', ', @ids), "\n";
output
1, 19, 40, 44, 48, 55, 100, 100, 199
Update
A large chunk of the code in the solution above is there to work out how to extract the list of values from a data reference. Recent versions of Perl have an experimental facility that allows you to use the values
operator on both hashes and arrays, and also on references ro both, so if you're running version 14 or later of Perl 5 and are comfortable disabling experimental warnings, then you can write ids
like this instead
use warnings;
use strict;
use 5.014;
sub ids {
my ($data) = @_;
return unless my $type = ref $data;
no warnings 'experimental';
if ( $type eq 'HASH' and exists $data->{id} ) {
$data->{id}, map ids($_), values $data;
}
else {
map ids($_), values $data;
}
}
The output is identical to that of the previous solution
Upvotes: 0