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

Dear Gurus,
How can I sort this Hash:
my %HoHoA = ( 'set1' => { 'AAAAAAA' => [ ['1','BOOK'],['2','PENCIL'] ], 'BBBBBBB' => [ ['0','CHALK'],['4','PEN'] ], }, # length of 2nd keys = 7 (all the same in 'set1') 'set2' => { 'AAA' => [ ['1','BOOK'],['2','PENCIL'] ], 'BBB' => [ ['0','CHALK'],['4','PEN'] ], }, # length of 2nd keys = 7 (all the same in 'set2') 'set3' => { 'AAA' => [ ['1','BOOK'],['2','PENCIL'] ], 'BBB' => [ ['0','CHALK'],['4','PEN'] ], }, # length of 2nd keys = 3 (all the same in 'set3') 'set4' => { 'AAAA' => [ ['1','BOOK'],['2','PENCIL'] ], 'BBBB' => [ ['0','CHALK'],['4','PEN'] ], }, # length of 2nd keys = 4 (all the same in 'set4') );
Based on the length of its second key.Namely: AAAAAAA (7),AAA(3),AAAA(4). In ascending way such that finally it simply prints:
set2 # Length 3 set3 # Length 3 set4 # Length 4 set1 # Length 7
I'm stuck with this:
foreach my $set ( sort {#???} keys %HoHoA ) { print "$set\n"; }
Also keeping in mind that I may not now "what is" the second key. Please help. Thanks a lot for your time.

Replies are listed 'Best First'.
Re: Sorting HoHoA based on Length of Second Key
by Zaxo (Archbishop) on Jul 21, 2005 at 03:10 UTC

    Can there be keys of different length in one? Can there be a different number of keys?

    I'll write this to sort by mean key length. Let's just assign an array to the sorted keys so we don't have to worry about the print loop. A Schwartzian Transform seems like a good idea for this.

    my @by_mean = map { $_->[0] } sort { $a->[1] <=> $b->[1] } map { [$_, length(join '', keys %{$HoHoA{$_}})/keys(%{$HoHoA{$_}})] } keys %HoHoA;
    The ST lets you do the real calculation once per element, instead of repetitively in the sort comparisons.

    After Compline,
    Zaxo

      Dear Mr Zaxo,
      As for your questions:
      Can there be keys of different length in one?
      NO.
      Can there be a different number of keys?
      YES.
Re: Sorting HoHoA based on Length of Second Key
by Pied (Monk) on Jul 21, 2005 at 03:19 UTC
    Here is what I managed to get working:

    print foreach (sort {(length ((keys %{$HoHoA{$b}})[0])) <=> length((keys %{$HoHoA{$a}})[0]) } keys %HoHoA);

    It only works if all second keys are the same length (which is the case with your submitted code)
    Is that what you want?

    P!
Re: Sorting HoHoA based on Length of Second Key
by tlm (Prior) on Jul 21, 2005 at 03:52 UTC

    print "$_ # Length ", length( ( keys %{ $HoHoA{ $_ } } )[ 0 ] ), "\n" for map $_->[ 0 ], sort { $a->[ 1 ] <=> $b->[ 1 ] || $a->[ 2 ] <=> $b->[ 2 ] } map [ $_, length( ( keys %{ $HoHoA{ $_ } } )[ 0 ] ), substr( $_, 3 ) + ], keys %HoHoA; __END__ set2 # Length 3 set3 # Length 3 set4 # Length 4 set1 # Length 7
    If you have no preference on the ordering of ties, then the ST part can be a bit simpler:
    map $_->[ 0 ], sort { $a->[ 1 ] <=> $b->[ 1 ] } map [ $_, length( ( keys %{ $HoHoA{ $_ } } )[ 0 ] ) ], keys %HoHoA;
    These solutions assume that all the secondary hashes contain at least one key.

    the lowliest monk

      These solutions assume that all the secondary hashes contain at least one key.
      It's me again sir,

      Sorry to disturb you.
      how can I modify your snippet if there happen to be a 'primary' key that has no value, for example there is additional:
      'set5' =>{},
      Just like in my recent posting.
      I can always turn off warning, but it is still troubling. Hope to hear from you again.


      ---
      neversaint and everlastingly indebted.......

        Just change the expression

        ( keys %{ $HoHoA{ $_ } } )
        to
        ( keys %{ $HoHoA{ $_ } }, '' )
        That way the resulting list will always have at least one element (of length 0).

        the lowliest monk

Re: Sorting HoHoA based on Length of Second Key
by tphyahoo (Vicar) on Jul 21, 2005 at 08:37 UTC
    This is a solution with an integrated Test::More type test, that dies if keys have different lengths. (Since I am trying to improve my test fu.)

    This solution actually fails the test at the end, because the order it calculates disagrees with the order you specified, because it chose a different ordering for set2 and set3, which have the same "length." I think this failure is actually a good thing, because it reveals that there is no canonical ordering for the algorithm you want.

    It would be interesting to either change the algorithm or rewrite the test so that you get an "ok." But I am going to skip that part. :)

    #!/usr/bin/perl -w use strict; use warnings; use Test::More qw(no_plan); my $expected = 'set2 set3 set4 set1 '; my $HoHoA = { 'set1' => { 'AAAAAAA' => [ ['1','BOOK'],['2','PENCIL'] ], 'BBBBBBB' => [ ['0','CHALK'],['4','PEN'] ], }, # length of 2nd keys = 7 (all the same in 'set1') 'set2' => { 'AAA' => [ ['1','BOOK'],['2','PENCIL'] ], 'BBB' => [ ['0','CHALK'],['4','PEN'] ], }, # length of 2nd keys = 7 (all the same in 'set2') 'set3' => { 'AAA' => [ ['1','BOOK'],['2','PENCIL'] ], 'BBB' => [ ['0','CHALK'],['4','PEN'] ], }, # length of 2nd keys = 3 (all the same in 'set3') 'set4' => { 'AAAA' => [ ['1','BOOK'],['2','PENCIL'] ], 'BBBB' => [ ['0','CHALK'],['4','PEN'] ], }, # length of 2nd keys = 4 (all the same in 'set4') }; my $got = ''; foreach my $set ( sort { inner_key_length($a) <=> inner_key_length($b +) } ( keys %{$HoHoA} ) ) { $got = $got . "$set "; } sub inner_key_length { my $key = shift or die "no key"; my @keys = keys %{ $HoHoA->{$key} }; my $length_firstkey = length($keys[0]); for my $check_key ( @keys ) { die "inconsistent key length" unless $length_firstkey = lengt +h($check_key); } print "length $key is $length_firstkey\n"; return $length_firstkey; } is($got, $expected);
Re: Sorting HoHoA based on Length of Second Key
by salva (Canon) on Jul 21, 2005 at 12:23 UTC
    use Sort::Key qw(ikeysort); my @sorted_keys = ikeysort { length ( (%{$HoHoA{$_}})[0] ) } keys %HoHoA;
Re: Sorting HoHoA based on Length of Second Key
by Anonymous Monk on Jul 21, 2005 at 12:53 UTC
    This will sort on the length of the first key found in each inner hash, then ASCIIbetically on the keys of the main HoHoA for ties:
    my @sorted = map {substr $_,2} sort map { pack 'na*', length((%{$HoHoA{$_}})[0]), $_ } keys %HoHoA;

    This is a Guttman-Rosler Transform - it packs the length of the first key of each inner hash (assumption: each inner hash has all keys the same length - you can't guarantee to get an AA.. key rather than a BB.. key as the `first') into 2 bytes in network order (assumption: no inner hash key has length >65535) and appends the main hash key.

    The resulting list of strings can be sorted by the fast default sort and the substr throws away the two-byte length marker, leaving just the keys.

    HTH

    Jonathan