Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

Dear Perl Monks,

Please help out a relatively novice Perl devotee, with a problem which has been driving me nuts for the past few days:

I am trying to implement a tied NDMB_File hash, where each hash key can reference 1 - n records, each consisting of several fields. The obvious structure seems to be a hash, with each key referencing an array of records, and each record referencing an array of fields - a hash of arrays of arrays, in other words. Conceptually, I guess it would be something like this:

%db -> key1 -> [ [field1, field2, field3, field4] [field1, field2, field3, field4] [field1, field2, field3, field4] ] -> key2 -> [ [field1, field2, field3, field4] ] -> key3 -> [ [field1, field2, field3, field4] [field1, field2, field3, field4] ]
It seems like it should be do-able. However, I don't have much experience with hashes and references, and I can't seem to figure out how to access the elements correctly. Every time I start to think that I am storing the data correctly, I find that I still can't retrieve it! Consequently, I am not going to post any of my code, as at this point I don't trust any of it!

I need to be able to add and retrieve records, compare records, and loop through the entire hash to generate a comma-delimited dump of the database.

I would be deeply grateful for any assistance that the Monks could give me with this.

Thanks in advance for any help!

Graham Scott.

Replies are listed 'Best First'.
Re: Hash of arrays of arrays
by Golo (Friar) on Oct 05, 2004 at 18:12 UTC
    use strict; use warnings; my %hash; $hash{key1} = [ [qw(k1r1f1 k1r1f2 k1r1f3)], [qw(k1r2f1 k1r2f2 k1r2f3)], ]; $hash{key2} = [ [qw(k2r1f1 k2r1f2 k2r1f3)], [qw(k2r2f1 k2r2f2 k2r2f3)], [qw(k2r3f1 k2r3f2 k2r3f3)], ]; #get the first element of the second row of key1 print $hash{key1}[1][0]; #iterate over the third row in key2 print for (@{$hash{key2}[2]});
    You might want to take a look at the references tutorial
Re: Hash of arrays of arrays
by ikegami (Patriarch) on Oct 05, 2004 at 18:14 UTC

    Note: I don't know anything about NDMB_File, so I'm working with the structure you showed.

    For starters, Data::Dump is your debugging friend here:

    use Data::Dumper; print(Dumper(\%db));

    To move around your structure, have a look at these two snippets:

    foreach $key (keys(%db)) { for ($record_num=0; $record_num<@{$db{$key}}; $record_num++) { foreach $field (@{$db{$key}[$record_num]}) { printf("Record %d of %s is %s.\n", $record_num, $key, $field, ); } } }
    foreach $key (keys(%db)) { foreach $record (@{$db{$key}}) { foreach $field (@$record) { ... } } }

    To append a record to a given key,

    # From ref to array of fields: push(@{$db{$key}}, $record); -or- # From individual fields: push(@{$db{$key}}, [ $field1, $field2, $field3, $field4 ]); -or- # From a my'd array of fields: { my @fields = ...; push(@{$db{$key}}, \@fields); } -or- # From a array of fields that will change: push(@{$db{$key}}, [ @fields ]);

    Comma-delimited:

    # Gives: # key1,field1,field2,field3,field4 # key1,field1,field2,field3,field4 # key1,field1,field2,field3,field4 # key2,field1,field2,field3,field4 # key3,field1,field2,field3,field4 # key3,field1,field2,field3,field4 # # Doesn't handle commas in key or field. $,=','; $\=$/; foreach $key (keys(%db)) { foreach $record (@{$db{$key}}) { print $key, @$record; } } -or- # Gives: # key1,0,field1,field2,field3,field4 # key1,1,field1,field2,field3,field4 # key1,2,field1,field2,field3,field4 # key2,0,field1,field2,field3,field4 # key3,0,field1,field2,field3,field4 # key3,1,field1,field2,field3,field4 # # Doesn't handle commas in key or field. $,=','; $\=$/; foreach $key (keys(%db)) { for ($record_num=0; $record_num<@{$db{$key}}; $record_num++) { print $key, $record_num, @{$db{$key}[$record_num]}; } }
      Many thanks to all who contributed in answer to my query - with the help of the Perl Monks, I am now most of the way there (though I had to dump the database idea in favour of a flat file - such is life!).

      However, I do have one small puzzle remaining, which perhaps someone could help me with:

      In one place, I need to add another record to my array of records, but only after checking all other records associated with that key, to make sure that the record I am adding is not a duplicate.

      So I need to test if I am at the end of the array, by checking a counter variable against the number of elements in the records array for the current key. I would have thought that the correct code would be along the lines of:
      if ($i < @{$db{$key}}) ...
      This is similar to the 'for' loop in ikegami's excellent example code. And I have seen something similar used in other code samples concerning hash-of-arrays structures. But when I print out the value of '@{$db{$key}}', I get:
      ARRAY(0xfdb30)
      Obviously, I would have expected to get an integer. Does the fact that my array elements contain another level of arrays (unlike the other examples I have seen) mean that I need a different test? Or is this simply a side-effect of 'print'?

      Can I rely on using '@{$db{$key}}' in my test, or would anyone like to suggest something different??

      Thanks in advance for any further guidance you can give me!

      Graham Scott.

        Compare:

        $\=$/; @a = qw( d e f ); print(@a); # prints 'def' print(scalar(@a)); # prints '3' print(@a==3?'3':'!3'); # prints '3'

        print accepts a list as an argument. @array returns a list of its elements in a list context.

        < accepts scalars as arguments. @array returns the number of elements it has in a scalar context.

        So yes, you can rely on @{$db{$key}} for your test. If you want to make it clear to yourself, use scalar(@{$db{$key}}) instead.

Re: Hash of arrays of arrays
by BrowserUk (Patriarch) on Oct 05, 2004 at 18:25 UTC

    As far as I'm aware, NDBM doesn't support nested datastructures, only single level key/value pairs--unless you want get into using Storable to freeze/Thaw complex values.

    You may like to investigate DBM::Deep which does this in a very natural way via tieing (or OO).


    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "Think for yourself!" - Abigail
    "Memory, processor, disk in that order on the hardware side. Algorithm, algorithm, algorithm on the code side." - tachyon