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

Oh wise monks! I am sort of stumped.

I think I see Perl 5.6.1 sorting differently (sometimes) than Perl 5.8.8. (And no, I can't just use 5.8.8.) Specifically, it looks like 5.6.1 has an imperfect notion of keeping "ties" in the order received.

I would greatly appreciate additional eyes on the code below, looking for any stupid mistakes I've made. The really nutty thing is that if I take away the last hashref in @unordered the sucker passes in 5.6.1.

Here's the code:

# show that sorting has changed from 5.6.1 to 5.8.8 use strict; use warnings; use Test::More tests => 1; # I would expect ties to come out in the order in which they are # received. 5.8.8 seems to do that, 5.6.1 does not. my @unordered = ( { pos => 3, val => 0 }, { pos => 5, val => 1 }, { pos => 4, val => 0 }, { pos => 1, val => -1 }, { pos => 2, val => -1 }, { pos => 6, val => 1 }, { pos => 0, val => -2 }, ); my @ordered = sort { $a->{pos} <=> $b->{pos} } @unordered; my @result = sort { $a->{val} <=> $b->{val} } @unordered; is_deeply( \@result, \@ordered ); diag("Your Perl is $]."); diag( "expected val: " . join( ",", map { $_->{val} } @ordered ) ); diag( " result val: " . join( ",", map { $_->{val} } @result ) ); diag( "expected pos: " . join( ",", map { $_->{pos} } @ordered ) ); diag( " result pos: " . join( ",", map { $_->{pos} } @result ) );

Here's the output for v5.8.8 built for darwin-2level:

1..1 ok 1 # Your Perl is 5.008008. # expected val: -2,-1,-1,0,0,1,1 # result val: -2,-1,-1,0,0,1,1 # expected pos: 0,1,2,3,4,5,6 # result pos: 0,1,2,3,4,5,6

And here's the output for v5.6.1 built for i386-linux:

1..1 not ok 1 # Failed test at wtf.pl line 24. # Structures begin differing at: # $got->[5]{pos} = '6' # $expected->[5]{pos} = '5' # Your Perl is 5.006001. # expected val: -2,-1,-1,0,0,1,1 # result val: -2,-1,-1,0,0,1,1 # expected pos: 0,1,2,3,4,5,6 # result pos: 0,1,2,3,4,6,5 # Looks like you failed 1 test of 1.

Of course I'm trying to solve a slightly trickier problem, but this is about as reductive a demonstration as I could come up with. Using a sort subroutine is an option, so if this is a known quirk with a known workaround I'm all ears.

Many thanks in advance for suggestions, reprimands, &c.

Replies are listed 'Best First'.
Re: Is sorting different in Perl 5.6.1 than in Perl 5.8.8?
by Joost (Canon) on Nov 01, 2007 at 00:08 UTC
    Perl's sort() is not guaranteed to be stable.

    Perls v5.6 and earlier did not use a stable sort. Current perls (probably) do can use a stable sort, but by default probably don't (this is from the docs of the sort pragma: I haven't tested it**). From the current sort (function) docs:

    Perl 5.6 and earlier used a quicksort algorithm to implement sort. That algorithm was not stable, and could go quadratic. (A stable sort preserves the input order of elements that compare equal. Although quicksort's run time is O(NlogN) when averaged over all arrays of length N, the time can be O(N**2), quadratic behavior, for some inputs.) In 5.7, the quicksort implementation was replaced with a stable mergesort algorithm whose worst-case behavior is O(NlogN). But benchmarks indicated that for some inputs, on some platforms, the original quicksort was faster. 5.8 has a sort pragma for limited control of the sort. Its rather blunt control of the underlying algorithm may not persist into future Perls, but the ability to characterize the input or output in implementation independent ways quite probably will. See sort.
    In other words, if you want to have a stable sort, you need a 5.8+ perl and use the sort pragma.

    ** update (again):

    > perl -Msort=defaults -e'print sort::current' mergesort
    on my system (debian linux, perl5.8.8) which as I understand it means sort is stable by default on that perl.

      Ah, interesting, thanks.

      That pretty much answers the basic question, and in my real-life implementation I'm lucky enough to be able to demand a "tiebreaker" be specified if you care about sort stability.

Re: Is sorting different in Perl 5.6.1 than in Perl 5.8.8? (stable sort)
by lodin (Hermit) on Nov 01, 2007 at 15:08 UTC

    Beware that the sort pragma isn't lexically scoped. If you're in an environment where you can't rely on sort.pm you could use a transform to handle stability, assuming your memory can handle that.

    my @sorted = map $_->[0], sort { $a->[0]->{val} <=> $b->[0]->{val} or $a->[1] <=> $b->[1] } map [ $assorted[$_] => $_ ], 0 .. $#assorted ;

    lodin

Re: Is sorting different in Perl 5.6.1 than in Perl 5.8.8?
by salva (Canon) on Nov 02, 2007 at 10:35 UTC
    try using Sort::Key, it always performs stable sorts:
    use Sort::Key qw(ikeysort); my @ordered = ikeysort { $_->{pos} } @unordered; my @result = ikeysort { $_->{val} } @unordered;