Jump to content

Sort a 2D array on using strings and characters


Go to solution Solved by AspirinJunkie,

Recommended Posts

The title may not make sense so here is the explanation. I am creating a menu structure that is read from an ini file. This structure uses the > character to tell the creating function to either create a menu item or a sub menu. The First array column is the menu structure. The second is the command to execute when the menu is selected. In this ini the commands are empty.

If there is only one > character the text following it will be a menu item i.e. Menu>Menu item. Menu will be a top level menu and Menu item will be an item of that menu.

If there are two or more, then sub menus will be created i.e. Menu>Submenu>Menuitem. 

I am trying to sort the array so that it is alphabetically sorted and also sorted so that submenus are before the menu items in there respective top level menus. The menu creation function takes care of this and menus will be created in the order they are in the array.

I have attached the ini file with the menu info. The commented section is how the menu should be when the array is sorted. The code is as far as I got before the migraines started :D It sorts the sub menus. but the items aren't correct.

There must be a better more bulletproof way. 

Thanks

#include <Array.au3>
#include <StringConstants.au3>

; Read the section from the INI file
Local $lines = IniReadSection(@ScriptDir & '\quicklaunch - Copy.ini', 'usermenu')

; Sort the lines alphabetically (descending order)
_ArraySort($lines, 0, 1)

; Manually reorder lines based on the number of ">" characters
Local $count1 = ''
Local $count2 = ''
Local $s_Temp0 = ''
Local $s_Temp1 = ''

For $i = 1 To $lines[0][0]
    For $j = $i + 1 To $lines[0][0]
        $count1 = UBound(StringSplit($lines[$i][0], ">", $STR_NOCOUNT)) - 1
        $count2 = UBound(StringSplit($lines[$j][0], ">", $STR_NOCOUNT)) - 1

        If $count1 < $count2 Then
            $s_Temp0 = $lines[$i][0]
            $s_Temp1 = $lines[$i][1]
            $lines[$i][0] = $lines[$j][0]
            $lines[$i][1] = $lines[$j][1]
            $lines[$j][0] = $s_Temp0
            $lines[$j][1] = $s_Temp1
        EndIf
    Next
Next

_ArrayDisplay($lines, 'Sorted By Arrow')

 

QuickLaunch - Copy.ini

Edited by benners
Link to comment
Share on other sites

Hi @benners 👋 ,

why should line 31 and 32 below the four ...Promax>nn... lines? Which kind of sorting should this be? I do not understand the logic.

image.png.70efc50cbd055df46a864097d06326fb.png

Best regards
Sven

Stay innovative!

Spoiler

🌍 Au3Forums

🎲 AutoIt (en) Cheat Sheet

📊 AutoIt limits/defaults

💎 Code Katas: [...] (comming soon)

🎭 Collection of GitHub users with AutoIt projects

🐞 False-Positives

🔮 Me on GitHub

💬 Opinion about new forum sub category

📑 UDF wiki list

✂ VSCode-AutoItSnippets

📑 WebDriver FAQs

👨‍🏫 WebDriver Tutorial (coming soon)

Link to comment
Share on other sites

Hi Sven,

The lines with the Promax are sub menus under the 2 pasti top menu. The 51, 52 etc are items under that sub menu. Lines 31 and 32 are menu items under the 2 pasti top level menu. When the menu is created, I want the sub menus to be at the top and menu items underneath.

Hope that makes sense :'(

Thanks.

Link to comment
Share on other sites

3 minutes ago, benners said:

When the menu is created, I want the sub menus to be at the top and menu items underneath.

Ah okay, now I get it (hopefully) 😅 . Okay, hmm let's see ... .

Best regards
Sven

Stay innovative!

Spoiler

🌍 Au3Forums

🎲 AutoIt (en) Cheat Sheet

📊 AutoIt limits/defaults

💎 Code Katas: [...] (comming soon)

🎭 Collection of GitHub users with AutoIt projects

🐞 False-Positives

🔮 Me on GitHub

💬 Opinion about new forum sub category

📑 UDF wiki list

✂ VSCode-AutoItSnippets

📑 WebDriver FAQs

👨‍🏫 WebDriver Tutorial (coming soon)

Link to comment
Share on other sites

I think I have sorted it, in that it sorts the way I want it to now. It will only sort by the first sub menu. Heads gone from thinking about just the first sub menu and not nested ones.

#include <Array.au3>

; Read the section from the INI file
Local $as_Lines = IniReadSection(@ScriptDir & '\quicklaunch - copy.ini', 'usermenu')

; add an extra column for number sorting
_ArrayColInsert($as_Lines, 2)

; Sort the lines alphabetically (descending order)
_ArraySort($as_Lines, 0, 1)

; Manually reorder lines based on the number of ">" characters
Local $s_CurrentMenu = ''
Local $s_NewMenu = ''
Local $i_SubMenu = 1
Local $i_MenuStart = 1
Local $i_SubMenuStart = 0

For $i = 1 To $as_Lines[0][0]
    ; get the top level menu text
    $s_NewMenu = StringRegExp($as_Lines[$i][0], "([^>]+)", 1)[0]

    ; if this is a new menu then
    If $s_CurrentMenu <> $s_NewMenu Then
        $s_CurrentMenu = $s_NewMenu ;_  Set the current menu text
        $i_MenuStart = $i ;_            Set the position the menu starts at
        $i_SubMenu = $i_MenuStart ;_    Set the starting menu position for the next sub menu
        $i_SubMenuStart = 0 ;_          reset the sub menu position
    EndIf

    If (UBound(StringSplit($as_Lines[$i][0], ">", $STR_NOCOUNT)) - 1) >= 2 Then ; assume sub menu
        If $i_SubMenuStart = 0 Then $i_SubMenuStart = $i ; set the first sub menu position
        $as_Lines[$i][2] = $i_SubMenu ; set the sub menus posiiton on the top level menu

;~      ; re-number the other menu items
        For $j = $i_MenuStart To $i_SubMenuStart - 1
            If $j = 1 Then ; if this is the first array element, increment the menu position by 1
                $as_Lines[$j][2] = $i_SubMenu + 1
            Else ;
                $as_Lines[$j][2] = $as_Lines[$j][2] + 1
            EndIf
        Next

        $i_SubMenu += 1 ; increase the sub menu
    Else
        $as_Lines[$i][2] = $i ; write the menu position for the menu item
    EndIf
Next

_ArraySort($as_Lines, 0, 1, 0, 2)
_ArrayDisplay($as_Lines, 'Sorted By Arrow')

 

Link to comment
Share on other sites

  • Solution

In principle, you can sort everything if you only have the right comparison function.
However, this is a bit tricky in your case. I have created a corresponding function from your specifications.

Now you just need a sort function that accepts a user-defined comparison function.
I have used the one from my >>ArrayPlus UDF<< for this and can use it to generate your desired result:

#include "ArrayPlus.au3"

; Read the section from the INI file
Local $aLines = IniReadSection(@ScriptDir & '\quicklaunch - Copy.ini', 'usermenu')

_ArrayDisplay($aLines, "before")
_ArraySortFlexible($aLines, __comp_benners, 1)
_ArrayDisplay($aLines, "sorted")

; comparison function, which decide which element of two elements of your data is bigger or lesser then the other
Func __comp_benners(ByRef $A, ByRef $B)
    Local $aSplitA = _regex_split($A[0], '^\d+\K\s*|>')
    Local $aSplitB = _regex_split($B[0], '^\d+\K\s*|>')
    Local $nA = UBound($aSplitA), $nB = UBound($aSplitB)
    Local $nMin = $nA > $nB ? $nB : $nA
    Local $bComp

    For $i = 0 To $nMin - 2
        $bComp = StringCompare($aSplitA[$i], $aSplitB[$i])
        If $bComp <> 0 Then Return $bComp
    Next

    Return $nA = $nB ? StringCompare($aSplitA[$i], $aSplitB[$i]) : $nA > $nB ? -1 : 1
EndFunc

; like StringSplit - but with regex to choose the delimiter
Func _regex_split($sString, Const $sSplitPattern, Const $dFlag = 3)
    Return StringSplit( _
        StringRegExpReplace($sString, $sSplitPattern, Chr(0)), _
        Chr(0), $dFlag)
EndFunc

 

Link to comment
Share on other sites

That works great thanks. It even sorts the nested sub menus. I have no idea how it does it. I have taken a look at the udf and still none the wiser :sweating:.

I do have a question about some of the functions. A lot have the Const declaration in the optional parameters but when the function is called, these parameters are changed. How does that work? I have never seen this, or can't remember seeing this style before.

Thanks again

Link to comment
Share on other sites

18 hours ago, benners said:

It even sorts the nested sub menus. I have no idea how it does it. I have taken a look at the udf and still none the wiser

You won't find the answer in the UDF code either. There is just a normal array sorting function (with an exchangeable comparison function). The interesting part is in the __comp_benners() function.

Sorting is in most cases based on a size comparison of 2 elements. The sorting function goes through the array in a certain pattern - depending on the sorting algorithm - and must always decide which of the two elements it is currently holding is larger than the other.

This means that the comparison function is responsible for what the result of a sort looks like. The sorting algorithm itself is only responsible for how to get there.

So how do you decide for your case whether an entry in your array is larger (i.e. should be inserted after the smaller element)?
In my approach, I take the entries and split them first (regex_split) so that “3 Pasti>Promax>861 & 862” becomes an array with the following content: |3|Pasti|Promax|861 & 862|.
The same applies to the comparison value. For example, “3 Pasti>Returns>51” becomes |3|Pasti|Returns|51|.
Now the two arrays are compared element by element:
3 = 3 - so nothing is returned yet
Pasti = Pasti - so return nothing yet
Promax < Returns - so return -1 (means A<B)

This is how you work your way through your hierarchy levels.
Now comes your special case: If the array sizes are different (because one is a menu and another is a submenu), then this comparison is only carried out up to the second last element. If A has one more element than B, it is returned -1 so that it is sorted before B.

As I said, the UDF is not so important - what is important is a suitable comparison function.

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

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...