Beefy Boxes and Bandwidth Generously Provided by pair Networks
Just another Perl shrine
 
PerlMonks  

Date to be sorted in descending and time in ascending

by karthik7887 (Initiate)
on May 18, 2012 at 08:56 UTC ( [id://971240]=perlquestion: print w/replies, xml ) Need Help??

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

I have a requirement to sort a set of records that come in an array. The sorting criteria for that array is all most current records (by date) should come first followed by older ones. If there are multiple records on same day, then older record should come first followed by newer records...

Example:
If I have records like this..

$dateArray[0] =20010405000000; $dateArray[1] =20050405005000; $dateArray[2] =20020405081200; $dateArray[3] =20080405022500; $dateArray[4] =20080405022600; $dateArray[5] =20080405023500; $dateArray[6] =20090405022500; $dateArray[7] =20090405022300; $dateArray[8] =20090405022900; $dateArray[9] =20090405022100;


After sorting It should contain elements in below order..
20090405022100 20090405022300 20090405022500 20090405022900 20080405022500 20080405022600 20080405023500 20050405005000 20020405081200 20010405000000

Replies are listed 'Best First'.
Re: Date to be sorted in descending and time in ascending
by RichardK (Parson) on May 18, 2012 at 09:38 UTC
Re: Date to be sorted in descending and time in ascending
by roboticus (Chancellor) on May 18, 2012 at 09:48 UTC

    karthik7887:

    You just need a custom sort method. Break the string into the date part and time part, and give the proper comparisons. Something like this:

    my @sorted = sort { substr($b,0,8) <=> substr($a,0,8) || substr($a,8) +<=> substr($b,8) } @unsorted;

    The first chunk breaks the date part off of the string, and sorts it in reverse order (note $b is on the left, $a on the right). In the case when the dates are the same, it breaks the time off the string and sorts it in ascending order ($a on the left, $b on the right).

    ...roboticus

    When your only tool is a hammer, all problems look like your thumb.

      This means that the substr function will be called four times for each comparison, which could add up if it's a large array. That makes it a good candidate for a Schwartzian Transform. (You probably know that; I'm adding it for the original poster.) UPDATE: I was completely wrong about this; see below.

      #!/usr/bin/env perl use Modern::Perl; use Data::Dumper; my @data = <DATA>; @data = map { $_->[0] } sort { $b->[1][0] <=> $a->[1][0] or $a->[1][1] <=> $b->[1][1] } map { [ $_, [substr( $_, 0, 8 ), substr( $_, 8)]] } @data; say @data; __DATA__ 20090405022300 20080405022600 20090405022900 20080405023500 20050405005000 20080405022500 20090405022500 20020405081200 20010405000000 20090405022100

      UPDATE: While I understand the Schwartzian Transform in theory and think it's one of the coolest things ever, I haven't had much call to actually use it, so I did a benchmark for this case against the simple sort of substr calls. I was a little surprised to see that the repeated substr calls beat my ST, testing with array sizes from 10 to 1_000_000. In fact, the ST took about twice as long in all tests. I guess four substr calls (or two if the first comparison returns a value so the second comparison isn't necessary) don't qualify as expensive enough to make the overhead of the ST worth it here. Darn it.

      Aaron B.
      Available for small or large Perl jobs; see my home node.

        aaron_baugher:

        Hmmm, I'm surprised. I figured at a million strings that the Schwartzian Transform would win. But you show another good lesson: Measure, don't guess. While both you and I expected the transform to win at a million strings, measurement trumps expectation.

        ...roboticus

        When your only tool is a hammer, all problems look like your thumb.

Re: Date to be sorted in descending and time in ascending
by kcott (Archbishop) on May 18, 2012 at 09:48 UTC

    The basic technique for sorting on multiple columns in a set of records (an array of arrays) is:

    my $primary_sort_column = n; my $secondary_sort_column = n; my $tertiary_sort_column = n; my @sorted_records = sort { $a->[$primary_sort_column] op $b->[$primary_sort_column]; || $a->[$secondary_sort_column] op $b->[$secondary_sort_column]; || $a->[$tertiary_sort_column] op $b->[$tertiary_sort_column]; ) @unsorted_records;

    n is an integer; op is typically cmp (lexical ordering) or the spaceship operator <=> (numeric ordering)

    Take a look at sort for details.

    -- Ken

Re: Date to be sorted in descending and time in ascending
by tobyink (Canon) on May 18, 2012 at 17:02 UTC

    Sort::Key is pretty awesome for this kind of thing.

    use 5.010; use strict; use Sort::Key qw(multikeysorter); my $sorter = multikeysorter( sub { /^(.{8})(.{6})/ }, # how to split into keys qw( -int int ), # how to sort each key ); my @dateArray = grep { 1+chomp } <DATA>; say for $sorter->(@dateArray); __DATA__ 20010405000000 20050405005000 20020405081200 20080405022500 20080405022600 20080405023500 20090405022500 20090405022300 20090405022900 20090405022100
    perl -E'sub Monkey::do{say$_,for@_,do{($monkey=[caller(0)]->[3])=~s{::}{ }and$monkey}}"Monkey say"->Monkey::do'
      or...
      use Sort::Key::Maker sort_dates => sub { /(.{8})(.{6})/ }, qw(-int int +); my @sorted = sort_dates(@dateArray);
Re: Date to be sorted in descending and time in ascending
by salva (Canon) on May 18, 2012 at 17:34 UTC
    substr($_, 0, 8) =~ tr/0-9/9876543210/ for @dateArray; @dateArray = sort @dateArray; substr($_, 0, 8) =~ tr/0-9/9876543210/ for @dateArray;
Re: Date to be sorted in descending and time in ascending
by johngg (Canon) on May 18, 2012 at 21:04 UTC

    A GRT just for some variety.

    knoppix@Microknoppix:~$ perl -E ' > my @dates = qw{ > 20010405000000 > 20050405005000 > 20020405081200 > 20080405022500 > 20080405022600 > 20080405023500 > 20090405022500 > 20090405022300 > 20090405022900 > 20090405022100 > }; > > say for > map { substr $_, 8 } > sort > map { pack q{NNA*}, 0 - substr( $_, 0, 8 ), 0 + substr( $_, 8 ), + $_ } > @dates;' 20090405022100 20090405022300 20090405022500 20090405022900 20080405022500 20080405022600 20080405023500 20050405005000 20020405081200 20010405000000 knoppix@Microknoppix:~$

    Cheers,

    JohnGG

Re: Date to be sorted in descending and time in ascending
by Cristoforo (Curate) on May 18, 2012 at 23:27 UTC
    Using unpack in a Schwartzian Transform.

    #!/usr/bin/perl use strict; use warnings; use 5.014; print map {$_->[0]} sort {$b->[1] cmp $a->[1] || $a->[2] cmp $b->[2]} map {[ $_, unpack "a8a6", $_ ]} <DATA>; __DATA__ 20010405000000 20050405005000 20020405081200 20080405022500 20080405022600 20080405023500 20090405022500 20090405022300 20090405022900 20090405022100

      I wondered if someone would suggest unpack. I don't use it often enough to remember the format codes without checking the man page, but it seems like it's often the fastest solution for this kind of thing. So I added yours to my benchmark, and found that unpack was slightly slower than substr/substr in this case. (I'd guess that if it were necessary to break the string into three or more pieces, unpack would come out ahead.) Both were still slower than the non-Schwartzian substr/substr sort, as detailed in my other post, though. Results and code:

      bannor:~/work/perl/monks$ perl 971240.pl 1000000 s/iter stunpack stsubst plainsort stunpack 12.4 -- -10% -31% stsubst 11.2 11% -- -24% plainsort 8.54 46% 31% -- bannor:~/work/perl/monks$ cat 971240.pl #!/usr/bin/env perl use Modern::Perl; use Benchmark qw(:all); my @data; push @data, int(rand(1000000000000))+10000000000000 for (1..$ARGV[0]); cmpthese( 10, { 'stunpack' => \&stunpack, 'stsubst' => \&stsubst, 'plainsort' => \&plainsort, }); sub plainsort { my @d = sort { substr($b,0,8) <=> substr($a,0,8) or substr($a,8) <=> substr($b,8) } @data; } sub stsubst { my @d = map { $_->[0] } sort { $b->[1] <=> $a->[1] or $a->[2] <=> $b->[2] } map { [ $_, substr( $_, 0, 8 ), substr( $_, 8)] } @data; } sub stunpack { my @d = map { $_->[0] } sort { $b->[1] <=> $a->[1] or $a->[2] <=> $b->[2] } map { [ $_, unpack "a8a6" ] } @data; }

      Aaron B.
      Available for small or large Perl jobs; see my home node.

        You should add salva's offering to your benchmark.


        With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.

        The start of some sanity?

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://971240]
Approved by marto
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others admiring the Monastery: (4)
As of 2024-03-19 08:00 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found