Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl: the Markov chain saw
 
PerlMonks  

Sorting Puzzle

by willfould (Novice)
on Feb 20, 2007 at 21:03 UTC ( #601207=perlquestion: print w/replies, xml ) Need Help??
willfould has asked for the wisdom of the Perl Monks concerning the following question:

The following script produces unexpected results.. can someone identify why the sorted order is reversed? - W
---------------------------- #!/usr/local/bin/perl my(%test,$j)=(); $test{'2007030110300020070301133000'}++; $test{'2007030110300020070301143000'}++; foreach $j (sort{$a<=>$b}(keys(%test))) { print "Key: $j\n"; } ------- results ------ [root@localhost]# perl test_sorting.pl Key: 2007030110300020070301143000 Key: 2007030110300020070301133000

Replies are listed 'Best First'.
Re: Sorting Puzzle
by imp (Priest) on Feb 20, 2007 at 21:08 UTC
    The numbers are too large for numeric comparison without using bigint
      Thank you! I am creating a numeric sorting key on start/end dates and times. I was originally wanting to convert the times to unix timestamp but could not find a fast way to do that. With the unix timestamp, this would be trivial but require the extra conversion step. Is there a datetime to unix timestamp function?
        Datetime. It also provides sorting. It also provides intervals which sounds like what you're interested in.

        Less applicable to the question but more important, imho, is that it handles all the nasty edgecases that any developer who didn't write Datetime shouldn't be bothered to know about handling dates and times. This would include the situations when a minute can have 59 seconds, 61 seconds, and even 62 seconds. (Yes, those situations do exist in really odd circumstances, but it would suck if you accidentally ran into one of them, right?)


        My criteria for good software:
        1. Does it work?
        2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
        Time::Piece makes it fairly easy, but you would have to benchmark it against other solutions to see what works for you. How much data are you dealing with?
        use Time::Piece; use strict; use warnings; my $date = '20070301103000'; my $start = Time::Piece->strptime($date, "%Y%m%d%H%M%S")->epoch; print $start, "\n";
Re: Sorting Puzzle
by jdporter (Canon) on Feb 20, 2007 at 21:11 UTC

    Hash keys are strings, but you're comparing them as numbers. Ordinarily, perl would automatically convert to numbers since they're being used in numeric context, but they're too big. So you need to do both of the following:

    1. use bigint;
    2. force the strings to numbers by (e.g.) adding to zero. For example:

    sort { (0+$a) <=> (0+$b) }
    A word spoken in Mind will reach its own level, in the objective world, by its own weight
Re: Sorting Puzzle (just sort)
by tye (Sage) on Feb 20, 2007 at 22:20 UTC

    Your "numbers" don't appear to be variable-width so just sort w/o specifying any comparison routine and you'll get the desired order:

    foreach $j ( sort keys %test ) {

    Others have pointed out that your numbers are too big for accurate numeric comparisons in Perl. The notes about avoiding the quotes or hash keys being strings and having to use "0+" to force numeric interpretation make no sense to me, however. $a <=> $b already forces $a and $b to be interpretted as numbers, always.

    Your numbers require about 91 bits and Perl usually uses about 53 bits of mantissa for floating point numbers so numeric comparison on these values end up comparing truncated values like 2007030110300020000000000000 instead.

    - tye        

      $a <=> $b already forces $a and $b to be interpretted as numbers, always.
      It doesn't appear to with perl 5.8.2:
      use strict; use warnings; my $d1 = '2007030110300020070301133000'; my $d2 = '2007030110300020070301143000'; printf "plain = %d\n", ($d1 <=> $d2); { use bigint; printf "bigint = %d\n", ($d1 <=> $d2); } { use bigint; printf "bigint-0 = %d\n", (($d1 -0)<=> ($d2-0)); }
      Output:
      plain = 0 bigint = 0 bigint-0 = -1

        That doesn't show $d1 and $d2 being compared as not numbers and so doesn't contradict my point. I guess the other points are about getting bigint to magically turn numeric expressions into objects. I'm not a fan of such subtle magic and so didn't recommend the use of bigint.pm and see no real value for it here anyway. But thanks for indirectly clarifying those points.

        - tye        

      The notes about ... having to use "0+" to force numeric interpretation make no sense to me

      I'm not saying it makes sense to me, either. But the perl interpreter don't lie, if you get my drift. You have to do both things (use bigint and manually coerce the numeric conversion) for the numeric comparison to work properly. Try it yourself if you don't believe me.

      This is perl, v5.8.8 built for MSWin32-x86-multi-thread
      
      Binary build 819 [267479] provided by ActiveState 
      Built Aug 29 2006 12:42:41
      
      A word spoken in Mind will reach its own level, in the objective world, by its own weight
        Tye's statement was correct. Perl does indeed compare the two values numerically, but the magic of bigint is not triggered. The 0+ doesn't force numeric comparison in this case, it triggers bigint magic.
        But why would you recommend bigint rather than Math::BigInt in this case???
Re: Sorting Puzzle
by Tanktalus (Canon) on Feb 20, 2007 at 21:10 UTC

    Just a quick guess - but your numbers are probably too long for perl to do numerical comparisons with.

    I thought bigint might help - but you'd have to remove the quotes to get that to work. Otherwise, just change the <=> to cmp to do string comparisons.

Re: Sorting Puzzle
by Moron (Curate) on Feb 22, 2007 at 12:29 UTC
    For that matter, is there any particular reason for having to use <=>, given that cmp happens do the job properly for reverse-field date data (even though it is numeric!) ?

    -M

    Free your mind

Re: Sorting Puzzle
by Anonymous Monk on Feb 21, 2007 at 15:00 UTC
    What's troubling is that swapping $a and $b give the same results. done without the {$a<=>$b} properly sorts the keys, as does {$a cmp $b}

    I suspect the problem is that the numbers are treated as ascii, I noticed the problem that if treated as strict numbers then the numbers become floats and over-write one another in the hash, if you really need them as numbers then perhaps a conversion to hex would work.

    Since looks like dates, can you avoid this and stuff them differently into the hash ?

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://601207]
Approved by wfsp
Front-paged by GrandFather
help
Chatterbox?
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others avoiding work at the Monastery: (5)
As of 2017-11-23 05:46 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    In order to be able to say "I know Perl", you must have:













    Results (328 votes). Check out past polls.

    Notices?