Jump to content

How to sort version numbers


Recommended Posts

I have a list of filenames and their versions and I want to sort them by the versions.  However, they always sort like they were just strings.  The version numbers can have up to 4 components (eg: 1.22.3.4).  I want version '1.2.7' to sort ahead of '1.12.7', and '1.8 alpha' to sort ahead of '1.8 beta'


Here's my array of items to be sorted:

1.5 alpha.22.1\filexxx
    1.9\filexxx
    1.8beta\filexxx
    1.8alpha\filexxx
    1.6\filexxx
    1.62\filexxx
    1.5-alpha.2.1\filexxx
    1.2\filexxx
    1.11-beta\filexxx

I want the sorted list to look like this:

1.2\filexxx
    1.5-alpha.2.1\filexxx
    1.5 alpha.22.1\filexxx
    1.6\filexxx
    1.62\filexxx
    1.8alpha\filexxx
    1.8beta\filexxx
    1.9\filexxx
    1.11-beta\filexxx

But, what I end up with is this:

1.2\filexxx
    1.6\filexxx
    1.9\filexxx
    1.62\filexxx
    1.8beta\filexxx
    1.8alpha\filexxx
    1.11-beta\filexxx
    1.5 alpha.22.1\filexxx
    1.5-alpha.2.1\filexxx

Here is my test script:

#include <Debug.au3>
_DebugSetup(@ScriptName & "_debug.txt", False, 5, "")
_DebugOut("=============== " & @MON & "/" & @MDAY & "/" & @YEAR & " " & @HOUR & ":" & @MIN & ":" & @SEC & " ====================" & @CRLF)

#include <Array.au3>
#include <String.au3>

test()

Func test()
    Local $ar

    $ar = StringSplit("", "")
    _ArrayAdd($ar, "1.5 alpha.22.1\filexxx")
    _ArrayAdd($ar, "1.9\filexxx")
    _ArrayAdd($ar, "1.8beta\filexxx")
    _ArrayAdd($ar, "1.8alpha\filexxx")
    _ArrayAdd($ar, "1.6\filexxx")
    _ArrayAdd($ar, "1.62\filexxx")
    _ArrayAdd($ar, "1.5-alpha.2.1\filexxx")
    _ArrayAdd($ar, "1.2\filexxx")
    _ArrayAdd($ar, "1.11-beta\filexxx")

    dumparray($ar, "$ar - initial values")

    setupVerSort($ar) ; Prepend each item in the array with a formatted version number

    dumparray($ar, "$ar - after setup")

    _ArraySort($ar, 0, 1) ; Sort based on the formatted version numbers

    cleanupVerSort($ar) ; Strip off the formatted version numbers

    dumparray($ar, "$ar - after cleanup")

EndFunc   ;==>test

; For each item in the array, extract the version number part, format it for sorting
; and put the formatted version in front of the item string (separated by " + ")
Func setupVerSort(ByRef $ar)
    Local $ndx, $ndx2, $verIN, $verOUT,  $vpartIN, $vpartOUT, $parts, $maxdepth

    $maxdepth = 4

    For $ndx = 1 To UBound($ar) - 1
        $verIN = StringSplit($ar[$ndx], "\", 2)[0] ; Get the version number
        $parts = StringSplit($verIN, '.', 2) ; Split the components into the $parts array
        redim $parts[$maxdepth] ; Only look at $maxdepth version components

        ; Format each component to be a set character length (8)
        For $ndx2 = 0 To UBound($parts) - 1
            $vpartIN = $parts[$ndx2]
            If (StringRegExp($vpartIN, "[a-zA-Z]+")) Then

                $vpartOUT = StringRegExpReplace($vpartIN, "([a-zA-Z])*", "\0")

                logmsg("$vpartIN ==>" & $vpartIN & "<==, $vpartOUT ==>" & $vpartOUT & "<==")
                logmsg("")
            Else
                $vpartOUT = $vpartIN
            EndIf

            $parts[$ndx2] = StringFormat("%8s", $vpartOUT) ; Format this conponent to be 8 characters wide
        Next

        $verOUT = _ArrayToString($parts, ".", 0)
        $ar[$ndx] = $verOUT & " + " & $ar[$ndx] ; Prepend the formatted version
    Next

EndFunc   ;==>setupVerSort

Func cleanupVerSort(ByRef $ar)
    Local $x

    For $ndx = 1 To UBound($ar) - 1
        $x = StringInStr($ar[$ndx], " + ")
        $ar[$ndx] = StringTrimLeft($ar[$ndx], $x + 2) ; Strip off the formatted version
    Next
EndFunc   ;==>cleanupVerSort

Func logmsg($msg, $lnum = @ScriptLineNumber)
    Local $str = ":" & $lnum & ": " & $msg ; Caller's line number plus the caller's message
    
    ; Send to both the Console and to DebugOut()
    _DebugOut($str)
    ConsoleWrite($str & @CRLF)
EndFunc   ;==>logmsg

Func dumparray(ByRef $ar, $sTitle, $lnum = @ScriptLineNumber)
    Local $str = @CRLF & " -" & $lnum & "-" & " Dump of " & $sTitle & @CRLF

    If (IsArray($ar)) Then
        For $ndx = 0 To UBound($ar) - 1
            $str &= "         [" & $ndx & "] " & $ar[$ndx] & @CRLF
        Next
    Else
        $str = "<not an array>"
    EndIf

    logmsg($str)
EndFunc   ;==>dumparray

 

Link to comment
Share on other sites

Jos has one a few posts above Malkey's solution in this thread:

 

,-. .--. ________ .-. .-. ,---. ,-. .-. .-. .-.
|(| / /\ \ |\ /| |__ __||| | | || .-' | |/ / \ \_/ )/
(_) / /__\ \ |(\ / | )| | | `-' | | `-. | | / __ \ (_)
| | | __ | (_)\/ | (_) | | .-. | | .-' | | \ |__| ) (
| | | | |)| | \ / | | | | | |)| | `--. | |) \ | |
`-' |_| (_) | |\/| | `-' /( (_)/( __.' |((_)-' /(_|
'-' '-' (__) (__) (_) (__)

Link to comment
Share on other sites

I had a typo ("1.5 alpha" instead of "1.5-alpha").     @wraithdu's _ArrayNaturalSort works for me.  However, it's SLOW!  I put in a loop, calling _ArrayNaturalSort() and it took 15 seconds for 10 iterations:

Local $tt = _Timer_Init()

For $ndx = 1 To 10
    _ArrayNaturalSort($aVersionsAndReleases, 0)
Next

ConsoleWrite("time: " & _Timer_Diff($tt) / 1000 & @CRLF)

 

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...