Jump to content
AndyS19

How to sort version numbers

Recommended Posts

AndyS19

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

 

Share this post


Link to post
Share on other sites
AndyS19

I tried  to go with @wraithdu's _ArrayNaturalSort. , but it only sorted 2 version components, and I need at least 4

 

Share this post


Link to post
Share on other sites
iamtheky

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

 


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

Share this post


Link to post
Share on other sites
AndyS19

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)

 

Share this post


Link to post
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

  • Similar Content

    • ternal
      By ternal
      Hi,
      Recently I have had the need to do a sort and then do a second sort while the item of the first sort stays the same ( double sorting , first on column x then while column x is the same sort column y).
      I did not put much efffort into error checking but so far I did not need it.
      For my applications so far it works perfectly however if someone is willing I want to test this extensivly.
      If anyone has big lists of random stuff to sort could you try this out please?
      #include <Array.au3> ; #FUNCTION# ==================================================================================================================== ; Name ..........: _ArraySort_Double ; Description ...: ; Syntax ........: _ArraySort_Double (Byref $array[, $first_index = Default[, $second_index = Default[, $ascending = Default]]]) ; Parameters ....: $array - 2d array to sort. ; $first_index - [optional] first column to sort. Default is 0. ; $second_index - [optional] second column to sort. Default is 1. ; $ascending - [optional] ascending/descending. Default is 1. ; Return values .: 1 if no errors occured , -1 if errors occured ; Author ........: Ternal ; Remarks .......: Needs excessive testing. ; Related .......: _arraysort() ; =============================================================================================================================== Func _ArraySort_Double (byref $array, $first_index = Default, $second_index = Default, $ascending = Default) Local $temp_value Local $counter = 1 If UBound($array, $UBOUND_DIMENSIONS) <> 2 Then MsgBox(0, "error", "error") return -1 EndIf If $first_index = Default Then $first_index = 0 If $second_index = Default Then $second_index = 1 If $ascending = Default Then $ascending = 1 _ArraySort($array, $ascending, 0, 0, $first_index); you can alter settings of primary sort here If @error Then MsgBox(0, "error", @error) return -1 EndIf $temp_value = $array[0][$first_index] For $x = 1 to UBound($array, 1) - 1 If Mod( $x, 10000) = 0 Then ConsoleWrite("at " & $x & " of a total : " & UBound($array, 1) & @CRLF) If $array[$x][$first_index] = $temp_value Then $counter+= 1 If $x = UBound($array, 1) - 1 Then; do last line here(if last line is not a new item) _ArraySort($array, $ascending, $x - $counter, $x, $second_index);you can alter settings of secondary sort here(don't forget to place line 34 the exact same) If @error Then MsgBox(0, "error", @error) return -1 EndIf EndIf Else If $counter > 0 Then ;at least 2 of the same _ArraySort($array, $ascending, $x - $counter, $x - 1, $second_index);you can alter settings of secondary sort here(don't forget to place line 29 the exact same) If @error Then MsgBox(0, "error", @error) return -1 EndIf $counter = 1 EndIf EndIf $temp_value = $array[$x][$first_index] Next Return 1 EndFunc Kind regards, Ternal
    • TimRude
      By TimRude
      The help text for the _ArrayDisplay function says: "Clicking on a column header sort it."
      I'm wondering why the sorting of hex numbers (specifically window handles like 0x12345678) is so wonky.
      Sample script:
      #include <Array.au3> Local $aList = WinList() _ArrayDisplay($aList) Clicking on the Col1 header to sort by window handle doesn't properly sort the list.
      As a workaround, I'm sorting the array by window handle before displaying it using _ArraySort. But I still wonder what's up with the ListView sorting.
      Workaround:
      #include <Array.au3> Local $aList = WinList() _ArraySort($aList, 0, 1, 0, 1) _ArrayDisplay($aList)  
    • Skysnake
      By Skysnake
      I am tracking this topic by @LarsJ.  It is very advanced and overkill for what I am currently trying to do.
       
      Problem is this.
      Listview contains columns, one of which is right aligned and gets populated by float values, such as 123.99.  Some do not have decimals ie 124.00 and on sort gets truncated to 124.  Its obviously still the same value, but the display has reset.
      ; line below is for list VIEW ;..................................0.........1......2............ $cListView = GUICtrlCreateListView("CUSTOMER|AMOUNT|DESCRIPTION", 8, 152, 764, 279) GUICtrlSetBkColor($cListView, $GUI_BKCOLOR_LV_ALTERNATE) ; alternate between the listview background color and the listview item background color GUICtrlSetBkColor($cListView, $LVStdClr) ; Set the background color for the listview _GUICtrlListView_SetColumnWidth($cListView, 0, 120) ; -- the client name _GUICtrlListView_SetColumnWidth($cListView, 1, 90) ;-- the amount _GUICtrlListView_JustifyColumn($cListView, 1, 1) ; 1 - Text is right aligned _GUICtrlListView_SetColumnWidth($cListView, 2, 200) ; the description What I am looking for is something native and simple like a 
          _GUICtrlListView_SetColumnFormat($cListView, 1, "%.2f") ;  1 - column is stringformatted to "%.2f"
      So that after each sort it will appear as it was in the original rendering.
      Is there something like this? I have not been able to find a simple solution.

      Thanks.
      Skysnake
    • TheDcoder
      By TheDcoder
      Hi, I have a 2D array with 2 columns, the 1st column contains a "version string" and the 2nd column contains a generic string. I want to sort it in the descending order so the latest version comes first.
      #include <Array.au3> Local $aVersionsAndReleases[4][2] = [["0.2.8.9", "Release #1"], ["0.2.9.10", "Release #3"], ["0.2.9.11", "Release #4"], ["0.2.8.10", "Release #2"]] _ArraySort($aVersionsAndReleases, 1) ConsoleWrite(_ArrayToString($aVersionsAndReleases, ' - ')) _ArrayDisplay($aVersionsAndReleases) Unfortunately, _ArraySort isn't working here . This is the output generated by the script:
      0.2.9.11 - Release #4 0.2.9.10 - Release #3 0.2.8.9 - Release #1 0.2.8.10 - Release #2 The expected result should be:
      0.2.9.11 - Release #4 0.2.9.10 - Release #3 0.2.8.10 - Release #2 0.2.8.9 - Release #1 I am looking to develop an function which does this... but I don't know where to start . Can someone help me get started? Thanks in Advance!
      - TD.
    • PINTO1927
      By PINTO1927
       
      Hello guys,
      I'm working with a simple script, complementing a GUICtrlCreateListView which has 5 columns. I would order the first column so that the numbers are decreasing.
      #include <GuiListView.au3> #include <ListViewConstants.au3> #include <WindowsConstants.au3> #include <GuiConstantsEx.au3> #include <StructureConstants.au3> Opt('MustDeclareVars', 1) Global $search_LV, $B_DESCENDING GUICreate("ListView Sort by Column Click", 400, 300) $search_LV = GUICtrlCreateListView("String|Number|String", 2, 2, 394, 268) GUICtrlSendMsg($search_LV, $LVM_SETEXTENDEDLISTVIEWSTYLE, $LVS_EX_GRIDLINES, $LVS_EX_GRIDLINES) GUICtrlSendMsg($search_LV, $LVM_SETEXTENDEDLISTVIEWSTYLE, $LVS_EX_FULLROWSELECT, $LVS_EX_FULLROWSELECT) GUICtrlCreateListViewItem("line4|5|1A", $search_LV) GUICtrlCreateListViewItem("line5|4.50 |1B", $search_LV) GUICtrlCreateListViewItem("line10|4.0 |2C", $search_LV) GUICtrlCreateListViewItem("line3|23|01", $search_LV) GUICtrlCreateListViewItem("line2|0.34560 |09", $search_LV) GUICtrlCreateListViewItem("line1|1.0 |7A", $search_LV) GUICtrlCreateListViewItem("line1|0.1 |8C", $search_LV) GUICtrlCreateListViewItem("line1|97|5B", $search_LV) GUICtrlCreateListViewItem("line1|910|9B", $search_LV) GUICtrlCreateListViewItem("line1|99|11", $search_LV) GUICtrlCreateListViewItem("line1|990.99|06", $search_LV) _GUICtrlListView_SetColumnWidth($search_LV, 0, 75) _GUICtrlListView_SetColumnWidth($search_LV, 1, 75) _GUICtrlListView_SetColumnWidth($search_LV, 2, 75) GUISetState() GUIRegisterMsg($WM_NOTIFY, "WM_NOTIFY") ;~ _GUICtrlListView_RegisterSortCallBack($search_LV, False) While 1 Switch GUIGetMsg() Case $GUI_EVENT_CLOSE ExitLoop Case $search_LV ; Kick off the sort callback ;~ _GUICtrlListView_SortItems($search_LV, GUICtrlGetState($search_LV)) ;~ _GUICtrlListView_UnRegisterSortCallBack($search_LV) EndSwitch WEnd Func WM_NOTIFY($hWnd, $iMsg, $iwParam, $ilParam) #forceref $hWnd, $iMsg, $iwParam Local $hWndFrom, $iIDFrom, $iCode, $tNMHDR, $hWndListView, $tInfo $hWndListView = $search_LV ;~ If Not IsHWnd($search_LV) Then $hWndListView = GUICtrlGetHandle($search_LV) $tNMHDR = DllStructCreate($tagNMHDR, $ilParam) $hWndFrom = HWnd(DllStructGetData($tNMHDR, "hWndFrom")) $iIDFrom = DllStructGetData($tNMHDR, "IDFrom") $iCode = DllStructGetData($tNMHDR, "Code") Switch $hWndFrom ;~ Case $search_LV Case GUICtrlGetHandle($search_LV) Switch $iCode Case $LVN_COLUMNCLICK ; A column was clicked ConsoleWrite("Header geklickt" & @CRLF) Local $tInfo = DllStructCreate($tagNMLISTVIEW, $iLparam) _GUICtrlListView_UnRegisterSortCallBack($search_LV) Local $ColumnSorted = DllStructGetData($tInfo, 'SubItem') If $ColumnSorted = 1 Then ConsoleWrite('Numeric: ' & _GUICtrlListView_RegisterSortCallBack($search_LV, True) & @CRLF) Else ConsoleWrite('Literal: ' & _GUICtrlListView_RegisterSortCallBack($search_LV, False) & @CRLF) EndIf _GUICtrlListView_SortItems($search_LV, DllStructGetData($tInfo, 'SubItem')) EndSwitch EndSwitch EndFunc ;==>WM_NOTIFY Ordering the "String" column, you notice that the line "line10" is not ordered last. And 'possible to order the "String" column in descending order?
      thank you very much
×