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

I have the following code abstract:
use strict; my %tables = ( 'Table_1' => {'Schema' => 'dbo'}, 'Table_2' => {'Schema' => 'dbo'} ); my %fields = ( 'Table_1' => { 'Field_1' => {'Datatype' => 'int', 'Position' => 1}, 'Field_2' => {'Datatype' => 'char', 'Position' => 2}, 'Field_3' => {'Datatype' => 'int', 'Position' => 3} }, 'Table_2' => { 'Field_1' => {'Datatype' => 'float', 'Position' => 1} } ); foreach my $table (sort keys %tables) { foreach my $field (sort {$fields{$table}{'Position'}{$a} <=> $fields +{$table}{'Position'}{$b}} keys %{$fields{$table}}) { print "$table -> $field\n"; } }
This works in that the final "foreach" returns the fields in the order of the "Position" value but I don't understand why (I only got it to work by giving it a go!) or if it's safe to rely on. How does the hash of hashes find the "Position" value when the "fieldname" level has been skipped? That is:
$fields{$tablename}{$fieldname}{'Position'}
and
$fields{$tablename}{'Position'}
Can someone advise on why this works and whether it's safe to rely on? Thanks.

Replies are listed 'Best First'.
Re: Sorting a hash by a well buried key
by haukex (Archbishop) on Sep 25, 2019 at 22:26 UTC
    This works in that the final "foreach" returns the fields in the order of the "Position" value

    If you run this code multiple times, it should show that the ordering changes (because hash ordering is random), so you'll see it is actually not working - you probably just saw them in order by chance, or because you're on an older version of Perl where hash ordering was less random. If you turn on warnings, you'll see several "Use of uninitialized value in numeric comparison (<=>)" warnings, hinting that something is wrong (always Use strict and warnings!). And if you use Data::Dumper to look at the data structure after the sort, you'll see a new 'Position' => {} element in the data structure, which explains why the code did not crash: Perl autovivified a hash reference for you when you tried to access the nonexistent hash.

    How does the hash of hashes find the "Position" value when the "fieldname" level has been skipped?

    Note you're not actually skipping them - keys %{$fields{$table}} returns the keys such as ("Field_2", "Field_1", "Field_3"). This means that in your sort, that's what $a and $b will be. And now, if you look at your sort function and compare it to the data structure, perhaps you can see what's wrong: Change $fields{$table}{'Position'}{$a} to $fields{$table}{$a}{'Position'} (and the same for $b) and your code works fine :-) swl's tips for shortening the code a bit are useful, too. (See also the Basic debugging checklist.)

      Thanks haukex. Changing {'Position'}{$a} for {$a}{'Position'} solved the problem perfectly.
Re: Sorting a hash by a well buried key
by swl (Prior) on Sep 25, 2019 at 21:46 UTC

    You have a hash of hashes, so have a look at https://perldoc.perl.org/perldsc.html#HASHES-OF-HASHES for more details.

    You could also simplify the for-loop by dereferencing before the sort, and take advantage of implied quoting of hash keys. You may or may not like double sigil dereferencing, but I find them visually cleaner.

    foreach my $table (sort keys %tables) { my $href = $fields{$table}; foreach my $field (sort $href->{Position}{$a} <=> $href->{Position}{ +$b}} keys %$href) { print "$table -> $field\n"; } }

    Update a few minutes after posting:

    I neglected to comment on reliability. So long as your hashes al follow the same structure then your approach will be reliable. If you are missing the Position key then you will get warnings about undefined values which you will need to handle (make sure to use warnings as well as use strict.

    Update 2: haukex pointed out in the chatterbox that my updated code also does not work. See 11106707. That will hopefully teach me to post without testing...