tags:

views:

506

answers:

4

I have a list of dd/mm/yyyy dates stored in a hash that I need to sequentially print out in order of date by month. Here is the relevant excerpt.

use feature 'say';

for my $date (sort {$a cmp $b} keys %data) {
    say $date;
}

This outputs:

16/07/2008
16/08/2008
16/09/2008
17/07/2008
17/08/2008
17/09/2008, etc.

when what I need is:

16/07/2008
17/07/2008
16/08/2008
17/08/2008
16/09/2008
16/09/2008, etc.

How can I achieve this?

+6  A: 

Parse and sort:

sub get_month {
    my $date = shift;
    my ($d, $m, $y) = split m{/}, $date;
    return $m;
}

sort { get_month($a) <=> get_month($b) } @dates;

I would probably use DateTime, though, since I want to work with objects, not meaningless strings:

my $parser = DateTime::Format::Natural->new(format => 'dd/mm/yyyy');
sort map { $parser->parse_datetime($_) } @dates;
jrockway
Sweet. So one can embed subroutines inside the sort command. I didn't know that...
Zaid
"in order of date by month" - I assumed that meant sort by day withint month, and that "by month" actually meant by month within year.
ysth
@ysth: That's right.jrockway's solution doesn't do that, but it pointed me in the right direction.
Zaid
+11  A: 

You can do it using a Schwartzian Transform, like so:

for my $date( map  { $_->[0] }
              sort { $a->[1] <=> $b->[1] } 
              map  { [ $_, (split /\//, $_)[1] ] }
              keys %data ) { 
    ...

This takes each key of %data and puts it in an anonymous array where the first element is the key and the second is the middle field from splitting on /. Then it sorts by the second element of the array, and uses another map to get the original list of keys.

friedo
+7  A: 

If the format is a rigid 10 chars:

sort { substr($a,3,2) cmp substr($b,3,2) } @dates;
larelogio
I like brevity!
Zaid
You should be doing the Schwartzian Transform though so that you don't have to recompute the comparable parts twice for each element. Especially if the format is not rigid.
dlamblin
Premature optimization is the root of all evil. Don't you think that "substr" is going to be as fast or faster than a hash lookup?
jrockway
... especially since we don't know how many lines with dates there are and whether performance is in any way critical here.
innaM
Wouldn't a numeric compare (`<=>`) be better suited than a string compare (`cmp`)? As well as faster?
Jack M.
A <=> would be the only correct solution if we could get 5 for May and 12 for December, but that's not the case. I've stated that this solution is valid only for a rigid 10 chars format.The result of the substr is a 2 char string that can be compared with no additional conversion. I don't see how <=> could be faster, even if that mattered.
larelogio
+2  A: 

From perlfaq4's answer to How do I sort an array by (anything)?


Supply a comparison function to sort() (described in sort in perlfunc):

@list = sort { $a <=> $b } @list;

The default sort function is cmp, string comparison, which would sort (1, 2, 10) into (1, 10, 2). <=>, used above, is the numerical comparison operator.

If you have a complicated function needed to pull out the part you want to sort on, then don't do it inside the sort function. Pull it out first, because the sort BLOCK can be called many times for the same element. Here's an example of how to pull out the first word after the first number on each item, and then sort those words case-insensitively.

@idx = ();
for (@data) {
 ($item) = /\d+\s*(\S+)/;
 push @idx, uc($item);
    }
@sorted = @data[ sort { $idx[$a] cmp $idx[$b] } 0 .. $#idx ];

which could also be written this way, using a trick that's come to be known as the Schwartzian Transform:

@sorted = map  { $_->[0] }
 sort { $a->[1] cmp $b->[1] }
 map  { [ $_, uc( (/\d+\s*(\S+)/)[0]) ] } @data;

If you need to sort on several fields, the following paradigm is useful.

@sorted = sort {
 field1($a) <=> field1($b) ||
 field2($a) cmp field2($b) ||
 field3($a) cmp field3($b)
 } @data;

This can be conveniently combined with precalculation of keys as given above.

See the sort article in the "Far More Than You Ever Wanted To Know" collection in http://www.cpan.org/misc/olddoc/FMTEYEWTK.tgz for more about this approach.

See also the question later in perlfaq4 on sorting hashes.

brian d foy