abc
abc

Reputation: 113

Perl - retrieve values from a hash of hashes

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

Answers (2)

Sean
Sean

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

Borodin
Borodin

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

Related Questions