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

I have a moderately complicated data structure I want to sort according to somewhat esoteric rules. This seems to be exactly what Data::Sorting was designed for. I am having trouble figuring out how to apply the module tp my specific problem, though. I have read the docs on CPAN several times, but I always end up mildly confused about exactly what to do.

The subroutine that needs the sorted data is passed a reference to a hash. The keys of the hash are arbitrary numbers, and the values are arrays that hold information about a particular person. More precisely, the hash has this structure:

{number_1 => [last_name1, first_name1, middle_name1, other_stuff1], number_2 => [last_name2, first_name2, middle_name2, other_stuff2], . . . number_N => [last_nameN, first_nameN, middle_nameN, other_stuffN], }

I want to sort the keys (number_1 ... number_N) by the alphabetical ranking of the name in that key's value. As usual, the priority is last_name > first_name > middle_name. Nothingness should sort before everything else, and hyphens should sort before "A".

Since the previous paragraph ended up more confusing than I'd hoped, I have an example. Suppose I want to sort the keys of this hash:

(spaces added for clarity) {1 => [Frog, Fuzzy, A, other_stuff], 2 => [Frog, Fuzzy, , other_stuff], 3 => [Toad, Zola, Q, other_stuff], 4 => [Frogger, Anthony, J, other_stuff], 5 => [Frog-Toad, Berl, G, other_stuff]}

The keys should end up in the order (2, 1, 5, 4, 3)

I would really like to get this working within a week or so. Any help would be greatly appreciated.

___________
-- Fuzzy Frog

P.S. I do not mean to imply that simonm's docs are in any way deficient. I'm sure the problem lies entirely with me.

-- FF

Replies are listed 'Best First'.
Re: Data::Sorting
by davido (Cardinal) on May 27, 2004 at 16:23 UTC
    I'm not familiar with the module, but the logical short circuit OR operator can help here.

    Assume you have a hash as you've described above, called %people:

    my @sortedkeys = sort { $people{$a}->[0] cmp $people{$b}->[0] or $people{$a}->[1] cmp $people{$b}->[1] or $people{$a}->[2] cmp $people{$b}->[2] } keys %people;


    Dave

Re: Data::Sorting
by Limbic~Region (Chancellor) on May 27, 2004 at 16:26 UTC
    Fuzzy Frog,
    I am not familiar that module, but since I wrote Tie::Hash::Sorted, you might want to try:
    #!/usr/bin/perl use strict; use warnings; use Tie::Hash::Sorted; my $sort = sub { my $h = shift; [ sort { $h->{$a}[0] cmp $h->{$b}[0] || $h->{$a}[1] cmp $h->{$b}[1] || $h->{$a}[2] cmp $h->{$b}[2] } keys %$h ]; }; tie my %s_hash, 'Tie::Hash::Sorted', 'Sort_Routine' => $sort, 'Optimiz +ation' => 'values'; %s_hash = ( 1 => [ qw(Frog Fuzzy A other_stuff) ], 2 => [ 'Frog', 'Fuzzy', '', 'other_stuff' ], 3 => [ qw(Toad Zola Q other_stuff) ], 4 => [ qw(Frogger Anthony J other_stuff) ], 5 => [ qw(Frog-Toad Berl G other_stuff) ], ); print "$_\n" for keys %s_hash;
    It will automatically keep the hash sorted even if you add/delete keys or modify the key's value to a new anonymous hash. You will have to read TFM carefully if those array refs are not anonymous as some other code may modify their values behind the scense (which requires modifying the optimization type). If you want more info let me know - I am late for a meeting.

    Cheers - L~R

Re: Data::Sorting
by duff (Parson) on May 27, 2004 at 16:29 UTC
    Sorry, I've never looked at Data::Sorting but I don't think you really need it. You could just use something like this:

    @sorted_keys = sort { $hash{$a}[0] cmp $hash{$b}[0] || $hash{$a}[1] cmp $hash{$b}[1] || $hash{$a}[2] cmp $hash{$b}[2] } keys %hash;

      And if you have a lot of data and you want the sorting to be fast, you can use:

      my @sortedKeys= map { ( split /\0/, $_ )[-1] } sort map { join "\0", @{$hash{$_}}[0,1,2], $_ } keys %hash;

      assuming no "\0" characters in your keys and values (often a pretty safe bet).

      - tye        

      Updated to replace original line

      map { join "\0", $hash{$_}[0,1,2], $_ }

      with corrected line. Thanks, BrowserUk.

        Don't you need to dereference the hash value to get the array slice to work?

        map { join "\0", @{ $hash{$_} }[0,1,2], $_ }

        Examine what is said, not who speaks.
        "Efficiency is intelligent laziness." -David Dunham
        "Think for yourself!" - Abigail
Re: Data::Sorting
by simonm (Vicar) on May 28, 2004 at 00:29 UTC
    I'm the author of Data::Sorting, and I can report that it doesn't really handle this situation well. (If you were sorting the arrays themselves, Data::Sorting would let you express it very concisely, but for sorting the keys by the values it's not so great.)

    You can get something close to what you want, but as the other solutions here have shown, it may be easier to do the sorting yourself.

    my $hashref = { 1 => [Frog, Fuzzy, A, other_stuff], 2 => [Frog, Fuzzy, '', other_stuff], 3 => [Toad, Zola, Q, other_stuff], 4 => [Frogger, Anthony, J, other_stuff], 5 => [Frog-Toad, Berl, G, other_stuff] }; use Data::Sorting 'sort_function'; my $sort_function = sort_function( map { my $x = $_; sub { $hashref->{ $_[0] }->[ $x ] } } 0, 1, 2); my @keys = $sort_function->( keys %$hashref ); print join ', ', @keys;