tags:

views:

118

answers:

3

Assuming I have a hash like this:

$hash_ref = {

  'hashes' => {
    'h1' => { 'klf' => '1', 'moomooland' => '1' },
    'h2' => { 'klf' => '2', 'moomooland' => '2' },
    'h3' => { 'klf' => '3', 'moomooland' => '3' }
  },

  'keys' => {
    'k1' => 'key1',
    'k2' => 'key2',
    'k3' => 'key3'
  }

}

How could I find out, as easy as possible, that hashes contains 3 more hashes, while keys contains 3 key/value pairs?

ref will return HASH for both of them, and I am not sure if it is possible to maybe find out the depth of these hashes.

Thanks :)

+3  A: 

If you just need to know if a hash is multi-dimensional, you can iterate over its values and stop if a reference is found:

my $is_multi_dimensional = 0;

for my $value (values %$hash_ref) {
    if ('HASH' eq ref $value) {
        $is_multi_dimensional = 1;
        last;
    }
}

or you can use the each() function:

while (my (undef, $value) = each %$hash_ref) {
    if ('HASH' eq ref $value) {
        $is_multi_dimensional = 1;
        last;
    }
}
eugene y
Oh, this is quite brilliant. So simple, yet so logical and effective. Argh, why didn't I think of this? :) Thank you Eugene, this works perfectly well for what I need, although now I realize that wording of my question doesn't really explain that this is what I was looking for, rather than getting exact/actual depth of a hash. Whoops.
sentinel
Are you sure each() is any more efficient that values()? Since it uses the internal iterator, it may short circuit which helps for giant hashes, but it has the general drawback of using the (global!) hash iterator. each() is a misfeature, IMO.
tsee
@tsee: it's more efficient for bigger hashes, because it doesn't create an intermediate list of values. And it's the only way to go for very big (e.g. tied) hashes. Of course, one should remember that there's a single iterator for each hash in the program, by design.
eugene y
It is only multidimensional if the ref points to another hash; or, arguably another array.. It certainly wouldn't be multidimensional just because it has a globref, subref, or scalar ref.. This isn't right.
Evan Carroll
@eugene y -- Actually, neither does `values` it creates a list of **references** to the values of the hash. `perl -e'my %foo=(bar=>'baz'); $_=1 for values %foo; use Data::Dumper; die Dumper \%foo'` and at that, yours does in fact, require the copying of each and key and value to a different local scalar namely `$key`, and `$value`.
Evan Carroll
@Evan: good points, fixing.
eugene y
+1  A: 

You can try this one to know the depth of Perl hash:

#!/usr/bin/perl
use strict;
my $hash_ref = {

  'hashes' => {
    'h1' => { 'klf' => '1', 'moomooland' => '1' },
    'h2' => { 'klf' => '2', 'moomooland' => '2' },
    'h3' => { 'klf' => '3', 'moomooland' => '3' }
  },

  'keys' => {
    'k1' => 'key1',
    'k2' => 'key2',
    'k3' => 'key3'
  }

};
print_nested_hash($hash_ref, 0);

sub print_nested_hash {
    my $hash_ref = shift;
    my $depth = shift;
    foreach my $key (sort keys %{$hash_ref}) {
        print '    ' x $depth, $key, "\n";

        if (ref($hash_ref->{$key}) eq "HASH") {
            print_nested_hash($hash_ref->{$key}, $depth+1);
        }
    }
} 

OUTPUT:

hashes
    h1
        klf
        moomooland
    h2
        klf
        moomooland
    h3
        klf
        moomooland
keys
    k1
    k2
    k3
Nikhil Jain
+5  A: 

You can also first and grep for this:

use List::Util 'first';
say 'is multi-dimensional' if first { ref } values %$hash_ref;

# explicitly check for HASH ref this time
my $how_many_md_hashes = grep { 'HASH' eq ref } values %$hash_ref;

NB. first (part of List::Util core module) short circuits so is ideal for conditionals and is probably the fastest of all possible options.

/I3az/

draegtun
You can use 'any' instead of 'first' if you only care about truthiness rather than the actual value that matched: `use List::MoreUtils 'any'; my $is_multi_dimensional = any { ref } values %$hash_ref;`
Ether