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

Hello Monks,
I have a hash and I want to sort it by its values, date, the date format would be MM/DD/YYYY.

Please look at my example below and see why it doesn't work. Please help!!!

sub by_date { my ($monA, $monB, $dayA, $dayB, $yearA, $yearB); ($monA, $dayA, $yearA) = split(/\//, $a); ($monB, $dayB, $yearB) = split(/\//, $b); $yearA <=> $yearB or $monA <=> $monB or $dayA <=> $dayB } foreach $k (sort by_date %H) { ... }
Thank You

janitored by ybiC: Balanced <code> tags around code example, as per Monastery convention

Replies are listed 'Best First'.
Re: sorting a hash by its values, date
by ikegami (Patriarch) on Aug 16, 2004 at 23:31 UTC

    sort's second argument is a list. %H, as a list, would look like ('key2', 'value2', 'key1', 'value1', 'key3', 'value3'). That means that by_date would be provided not just with the values, but with the keys as well.

    Here's a solution:

    sub by_date { my ($monA, $monB, $dayA, $dayB, $yearA, $yearB); ($monA, $dayA, $yearA) = split(/\//, $a->[1]); # <-- slight change ($monB, $dayB, $yearB) = split(/\//, $b->[1]); # <-- slight change $yearA <=> $yearB or $monA <=> $monB or $dayA <=> $dayB } @H = map { [ $_, $H{$_} ] } keys(%H); foreach $k (sort by_date @H)) { # $k->[0] is the key. # $k->[1] is the value. ... }
Re: sorting a hash by its values, date
by Joost (Canon) on Aug 16, 2004 at 23:34 UTC
    Well, first of all you can't really sort a hash. A hash is always unsorted. Also, you're sorting the hash keys and values together, which is probably not what you want. If you want to process the hash values in a sorted order, do this:
    foreach $value (sort by_date values %H) { # ... }

    If you need the keys, sorted by their corresponding values:

    foreach $key (sort { by_date($H{$a},$H{$b}) } keys %H) { }
    If it still doesn't work, try showing some of the input and expected output of your code.

    By the way, code can be much easier formatted using <code> tags.

Re: sorting a hash by its values, date
by aquarium (Curate) on Aug 17, 2004 at 03:05 UTC
    i gather you should have the problem worked out by now, to sort the values of the hash. To make it much simpler to work with the dates in the hash, you could store YYYYMMDD format instead and treat as a number (not a string). Then, sorting and comparisons will be much simpler. Convert to MM/DD/YYYY only when needed, for display etc.
Re: sorting a hash by its values, date
by Eimi Metamorphoumai (Deacon) on Aug 16, 2004 at 23:25 UTC
    The problem is that you can't sort a hash, you sort an array of, for instance, the keys of the hash. So replacing sort by_date %H with sort by_date keys %H should work.
      Thank you so much for your answer. But the date is the values of the hash, not the keys of the hash.
Re: sorting a hash by its values, date
by Random_Walk (Prior) on Aug 17, 2004 at 08:56 UTC
    I have stolen ikegama's idea of using map along with aquarium's idea of reordering the date so it sorts easily and come up with the following little snippet. The date is re-ordered in the map block and munged together with the key to make it easy to sort the entire thing as an array. once sorted it is easy to reconstruct the data as you like.

    This does depend on the day, date and year being 2,2,4 digits, if they are not then a little more needs adding in the map block to fix this

    cheers.

    #!/usr/local/bin/perl -w use strict; my %h = ( key1 => "10/14/2003", key2 => "11/23/2001", key3 => "12/23/2001", key4 => "12/22/2001", key5 => "02/21/1984", key6 => "08/13/1969", key7 => "09/11/1973", key8 => "09/30/2000" ); my @bydate = map { my @tmp = split /\//, $h{$_}; $tmp[2].":".$tmp[0].":".$tmp[1].":".$_ } keys(%h); foreach (sort @bydate) { my ($yr, $mn, $dy, $key) = split /:/; print "$mn/$dy/$yr = $key\n"; }

    update
    I have been playing with pack/unpack and thought this was a nice place to use it. So here is a more efficient version
    #!/usr/local/bin/perl -w use strict; my %h = ( key1 => "10/14/2003", key2 => "11/23/2001", key3 => "12/23/2001", key4 => "12/22/2001", key5 => "02/21/1984", key6 => "08/13/1969", key7 => "09/11/1973", key8 => "09/30/2000" ); my @bydate = map { my @tmp = split /\//, $h{$_}; pack 'A4A2A2A*', $tmp[2], $tmp[0], $tmp[1], $_; } keys(%h); foreach (sort @bydate) { print "@{[unpack ('A4A2A2A*',$_)]}\n"; }