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

Greetings. I have an array resembling the following:
@versions=qw(2.0.0 1.0.9 1.0.10 1.1.0 1.1.2);
I need to sort the above array by version. A simple ascii sort will incorrectly put version 1.0.9 after 1.0.10 etc. My best guess is that i need to sort using a usersub that will split the versions on /./ into $maj,$min,$rev and use the <=> operator to sort on each in turn. If anybody knows of any modules that will make this easier...or simply a good starting point I would be much oblidged. Thanks in advance, Jason

Replies are listed 'Best First'.
Re: Sorting An Array of Versions
by dragonchild (Archbishop) on Mar 12, 2002 at 17:56 UTC
    Use the Schwartzian Transform.
    @version = map { $_->[0] } sort { $a->[1][0] <=> $b->[1][0] || $a->[1][1] <=> $b->[1][1] || $a->[1][2] <=> $b->[1][2] } map { [ $_, [ split '.', $_ ] ] } @versions;
    The idea is to create an array-reference, the first element is the thing and the second element is how you're going to sort. Sort the thing, then reconvert it back to the original. :-)

    ------
    We are the carpenters and bricklayers of the Information Age.

    Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

(tye)Re: Sorting An Array of Versions
by tye (Sage) on Mar 12, 2002 at 19:10 UTC

    I like:

    my @sorted= map { s/\0(....)/unpack("N",$1)/gs } sort map { s/(\d)/"\0".pack("N",$1)/g } @versions;
    (I assumed no "\0" characters in the version strings as it makes the code extra simple.) Note that this doesn't assume how many fields each version string has nor that you don't have version numbers like "1.0a".

    See also Sorting on Section Numbers (which includes why Sort::Versions might not be the best choice) and How do I do a natural sort on an array?.

            - tye (but my friends call me "Tye")
Re: Sorting An Array of Versions
by perrin (Chancellor) on Mar 12, 2002 at 18:49 UTC
Re: Sorting An Array of Versions
by dreadpiratepeter (Priest) on Mar 12, 2002 at 18:05 UTC
    This will work, assuming your version numbers and subnumbers don't exceed 1000. This is of course the famous Shwartzian Transform.
    #!/usr/local/bin/perl use strict; my @versions=qw(2.0.0 1.0.9 1.0.10 1.1.0 1.1.2); my @sversions = map {$_->[0]} sort {$a->[1] <=> $b->[1]} map { my ($a1,$a2,$a3) = split(/\./,$_); [$_,$a1*1000000 + $a2*1000 + $a3] } @versions;


    -pete
    "I am Jack's utter lack of disbelief"
Re: Sorting An Array of Versions
by I0 (Priest) on Mar 12, 2002 at 18:45 UTC
    sub ST(&@){ my $metric=shift; map {$_->[0]} sort {$a->[1] cmp $b->[1]} map {[$_,&{$metric}]} @_ } my @versions = qw(2.0.0 1.0.9 1.0.10 1.1.0 1.1.2); @versions = ST {pack'C*',split/\./} @versions;
Re: Sorting An Array of Versions
by blakem (Monsignor) on Mar 12, 2002 at 19:00 UTC
    Never mind, this doesn't actually work. it was just a fluke of the small data set I tried it against...

    Though I have to supress 'is not numeric' warnings, a simple numeric sort seems to produce the order you want....

    #!/usr/bin/perl -wT use strict; my @versions=qw(2.0.0 1.0.9 1.0.10 1.1.0 1.1.2 1.0.90); my @sorted = do {no warnings 'numeric'; sort {$a <=> $b} @versions }; print "$_\n" for @sorted; __END__ 1.0.9 1.0.10 1.0.90 1.1.0 1.1.2 2.0.0

    -Blake

Re: Sorting An Array of Versions
by Dog and Pony (Priest) on Mar 12, 2002 at 19:07 UTC
    Update: Looking at the other answers, seems I misunderstood the question? Please disregard.

    Update 2: tye correctly pointed out that what I was trying to do (failing badly) was this:

    sort{ eval "v$a" cmp eval "v$b" } @versions;
    which is probably too hacky. :)

    ----

    Since you have "version" strings, you can use the v notation to sort the strings, like this:

    sort{ "v$a" cmp "v$b" } @versions;
    This may seem a little hacky, but gets the job done.

    It vorks like this: v2.0.0 is the same thing as chr(2).chr(0).chr(0), so we can do a stringwise compare, cmp and thus sort these correctly. The result is:

    1.0.10 1.0.9 1.1.0 1.1.2 2.0.0
    and you can just switch $a and $b to reverse the order.


    You have moved into a dark place.
    It is pitch black. You are likely to be eaten by a grue.

      "v$a" does not a "vector" make (it just makes "v".$a). You'd have to use something like eval "v$a" for that. Even that won't work in the face of common version number formats like "1.0a".

      Note that "10" is showing as less than "9" in your results.

              - tye (but my friends call me "Tye")
        I noticed. I thought that was what was sought after, and stupidly did not crosscheck against what a direct cmp between the strings did - the same. I did realize my mistake... way too late. Better try to not be so triggerhappy in the future. :)
        You have moved into a dark place.
        It is pitch black. You are likely to be eaten by a grue.