• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!

[Algorithm] QuickSort<T>

Level 26
Joined
Mar 19, 2008
Messages
3,140
Implementation of QuickSort and InsertionSort for sorting elements of type <T>. Designed in way which enables user to provide his own comparator.

Note: default comparator is defined only for integers and reals thus other types and especially complex comparisons (struct instances comparison) require user to implement compare method on the top of given struct. Library is fool-proof. No comparator? Script block won't even load.

Note: compare method much use following syntax: static method compare_<type> takes <type> a, <type> b returns integer
Function should return integer value: 0, -1 or 1 depending on outcome.

Uses multiple modifications to improve efficiency when dealing with high amount of data. Recursion approach. Elements compared via less function.
To provide intuitive api and due to lack of pointers in jass I've decided to use Table as an array object. Since Array<T> and Vector<T> are child structs to Table, the .data() member allows to use sort in convenient way.

Special thanks to looking_for_help for helping me fix awkard issue with comparators.

Requires newest Table update.
Huge applause goes to Bribe for updating his amazing Table to better accommodate this resource.
JASS:
/*****************************************************************************
*
*    QuickSort v1.0.2.2
*       by Bannar aka Spinnaker
*
*    Sorts data of desired type using quick sort algorithm.
*
******************************************************************************
*    
*    Requirements:
*       Table by Bribe
*          hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/
*
*       Table requirement is needed due to lack of pointers within jass. Implementing intuitive
*       api requires object that can represent an array and which can be passed through
*       functions while changes made during procedure are actually saved within such object
*
******************************************************************************
*
*    Implementation:
*
*       macro DEFINE_QSORT takes NAME, TYPE, OBJECT, COMPARATOR
*
*          NAME - sets name for quick sort type struct
*          TYPE - type of values to sort
*          OBJECT - boolean, tells whether array stores struct objects or not
*          COMPARATOR - optional argument, however, if compare method is non existant
*                       sort code won't be implemented
*
******************************************************************************
*
*    struct API:
*
*       static constant string valueType
*
*       static method isort takes Table input, integer lo, integer hi returns nothing
*          performs insertion sort procedue on object input with interators range lo to hi
*
*       static method qsort takes Table input, integer lo, integer hi returns nothing
*          performs quick sort procedue on object input with interators range lo to hi
*
*       static method isortSimple takes Table input, integer size returns nothing
*          simple adaptation of isort; iterator lo is set to 0
*
*       static method qsortSimple takes Table input, integer size returns nothing
*          simple adaptation of qsort; iterator lo is set to 0
*
******************************************************************************/
library QuickSortTemplate requires Table

    globals
        /**
            defines how small table size within given iteration
            has to be before insertion sort is performed
        */
        constant integer QUICK_SORT_CUTOFF = 8
    endglobals

//! textmacro_once DEFINE_QSORT takes NAME, TYPE, OBJECT, COMPARATOR
struct $NAME$ extends array
    readonly static string valueType = "$TYPE$"

    implement optional $COMPARATOR$

    static if not thistype.compare.exists then
        static if LIBRARY_$TYPE$DefaultComparator then
            implement optional $TYPE$DefaultComparator
        endif
    endif

static if thistype.compare.exists then
    private static method Set takes Table table, integer index, $TYPE$ value returns nothing
        static if $OBJECT$ then 
            set table[index] = value
        else
            set table.$TYPE$[index] = value
        endif
    endmethod

    private static method get takes Table table, integer index returns $TYPE$
        static if $OBJECT$ then
            return table[index]
        else
            return table.$TYPE$[index]
        endif
    endmethod

    private static method less takes $TYPE$ a, $TYPE$ b returns boolean
        return ( thistype.compare(a, b) < 0 )
    endmethod

    private static method equal takes $TYPE$ a, $TYPE$ b returns boolean
        return ( thistype.compare(a, b) == 0 )
    endmethod

    private static method swapInput takes Table input, integer a, integer b returns nothing
        local $TYPE$ tmp = get(input, a)
        call Set(input, a, get(input, b))
        call Set(input, b, tmp)
    endmethod

    private static method median3 takes Table input, integer i, integer j, integer k returns integer
        if ( less( get(input, i), get(input, j) ) ) then
            if ( less( get(input, j), get(input, k) ) ) then
                return j
            else
                if ( less( get(input, i), get(input, k) ) ) then
                    return k
                else
                    return i
                endif
            endif
        else
            if ( less( get(input, k), get(input, j) ) ) then
                return j
            else
                if ( less( get(input, k), get(input, i) ) ) then
                    return k
                else
                    return i
                endif
            endif
        endif
    endmethod

    private static method insertionSort takes Table input, integer lo, integer hi returns nothing
        local integer i = lo
        local integer j
        loop
            exitwhen ( i > hi )
            set j = i
            loop
                exitwhen j <= lo or not less( get(input, j), get(input, j-1) )
                call swapInput(input, j, j-1)
                set j = j - 1
            endloop
            set i = i + 1
        endloop
    endmethod

    private static method quickSort takes Table input, integer lo, integer hi returns nothing
        local integer lenN = hi - lo + 1
        local integer median
        local integer eps
        local integer mid
        local integer mFirst
        local integer mMid
        local integer mLast
        local integer ninther

        local $TYPE$ value
        // iterators
        local integer iterI = lo
        local integer iterP = lo
        local integer iterJ = hi + 1
        local integer iterQ = hi + 1
        local integer k

        // less than cutoff? use insertion sort instead
        if ( lenN <= QUICK_SORT_CUTOFF ) then
            call insertionSort(input, lo, hi)
            return

        // use just median3 for small array
        elseif ( lenN <= 40 ) then
            set median = median3(input, lo, lo + lenN / 2, hi)
            call swapInput(input, median, lo)

        // Tukey ninther for finding partitioning element
        else
            set eps = lenN / 8
            set mid = lo + lenN / 2
            set mFirst = median3(input, lo, lo + eps, lo + eps + eps)
            set mMid = median3(input, mid - eps, mid, mid + eps)
            set mLast = median3(input, hi - eps - eps, hi - eps, hi)
            set ninther = median3(input, mFirst, mMid, mLast)
            call swapInput(input, ninther, lo)
        endif

        // sort using 3-way partitioning
        set value = get(input, lo)
        loop
            loop
                set iterI = iterI + 1
                exitwhen not less( get(input, iterI), value )
                if ( iterI == hi ) then 
                    exitwhen true
                endif
            endloop

            loop
                set iterJ = iterJ - 1
                exitwhen not less( value, get(input, iterJ) )
                if ( iterJ == lo ) then
                    exitwhen true
                endif
            endloop

            // cross pointers
            if ( iterI == iterJ and equal( get(input, iterI), value ) ) then
                set iterP = iterP + 1
                call swapInput(input, iterP, iterI)
            endif
            if ( iterI >= iterJ ) then
                exitwhen true
            endif

            call swapInput(input, iterI, iterJ)
            if ( equal( get(input, iterI), value ) ) then
                set iterP = iterP + 1
                call swapInput(input, iterP, iterI)
            endif
            if ( equal( get(input, iterJ), value ) ) then
                set iterQ = iterQ - 1
                call swapInput(input, iterQ, iterJ)
            endif

            exitwhen false
        endloop

        set iterI = iterJ + 1
        set k = lo
        loop
            exitwhen k > iterP
            call swapInput(input, k, iterJ)
            set iterJ = iterJ - 1
            set k = k + 1
        endloop

        set k = hi
        loop
            exitwhen k < iterQ
            call swapInput(input, k, iterI)
            set iterI = iterI + 1
            set k = k - 1
        endloop

        call quickSort(input, lo, iterJ)
        call quickSort(input, iterI, hi)
    endmethod

    static method isort takes Table input, integer lo, integer hi returns nothing
        call insertionSort(input, lo, hi)
    endmethod

    static method qsort takes Table input, integer lo, integer hi returns nothing
        call quickSort(input, lo, hi)
    endmethod

    static method isortSimple takes Table input, integer size returns nothing
        call insertionSort(input, 0, size-1)
    endmethod

    static method qsortSimple takes Table input, integer size returns nothing
        call quickSort(input, 0, size-1)
    endmethod
endif

endstruct
//! endtextmacro

endlibrary

// Default integer & real comparators
//! textmacro_once DEFINE_DEFAULT_COMPARATOR takes TYPE
library_once $TYPE$DefaultComparator
    module $TYPE$DefaultComparator
        static method compare takes $TYPE$ a, $TYPE$ b returns integer
            if ( a < b ) then
                return -1
            elseif ( a == b ) then
                return 0
            else // ( a > b )
                return 1
            endif
        endmethod
    endmodule
endlibrary
//! endtextmacro

//! runtextmacro DEFINE_DEFAULT_COMPARATOR("integer")
//! runtextmacro DEFINE_DEFAULT_COMPARATOR("real")
Demo:
JASS:
//! runtextmacro DEFINE_ARRAY("Array", "integer", "9", "0", "false")
//! runtextmacro DEFINE_QSORT("IntegerSort", "integer", "false", "")

struct qsort_struct extends array

    static method print takes Table tb, integer size, string prefix returns nothing
        local integer i = size
        local string s = ""
        loop
            exitwhen 0 == i
            set i = i-1
            set s = I2S(tb[i]) + ", " + s
        endloop
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, prefix + s)
        set s = null
    endmethod

    static method qsort_test takes nothing returns nothing
        local integer max = 9
        local integer i
        local string s
        local Array arr = Array.create()

        set arr[0] = 55
        set arr[1] = 3
        set arr[2] = 88
        set arr[3] = 9
        set arr[4] = -10
        set arr[5] = 1
        set arr[6] = 17
        set arr[7] = -1
        set arr[8] = 26

        call print(arr.data(), arr.size(), "Before: ")
        call IntegerSort.qsort(arr.data(), 0, 8)
        call print(arr.data(), arr.size(), "After: ")
    endmethod

    private static method onInit takes nothing returns nothing
        call TimerStart(CreateTimer(), 2, false, function thistype.qsort_test)
    endmethod
endstruct

module CompareStrings
    static method compare takes string a, string b returns integer
        if ( a == b ) then
            return 0
        elseif ( StringLength(a) < StringLength(b) ) then
            return -1
        else // ( a > b )
            return 1
        endif
    endmethod
endmodule

//! runtextmacro DEFINE_QSORT("StringSort", "string", "false", "CompareStrings")
 
Last edited:
I haven't looked at the code in depth, but the recursive approach makes me feel a little iffy. The recursive form is used widely in programming languages because recursion is very practical for them. For JASS, not so much (iirc). I bet you'd get better results from an iterative version.

And apart from that, it is a little strange to use Table for input, IMO. Hashtables are totally fine, but with lists of data, it is much more convenient to use arrays. Obviously, you can't take arrays as input, but I would've instead wrote a list object and sorted its data (or returned a new list with the sorted data).
 
Level 26
Joined
Mar 19, 2008
Messages
3,140
At first: I'm gratefull for feedback.

Algorithm is really quick. None will be sorting 10k++ in jass anyways thus I do not think recursion is a problem. Sorting list != quicksort ;/ If I understood you correctly, such approach does not allow user-friendly api. Here, sort does not care about type and can be called for anything as long as data is correctly stored within Table object.
Because of limitations brough by Jass, I had choose the best solution out of this situation.

And in regard to list, I got ForwardList<T> and List<T> and they feature TopDown merge sort; both are almost finished, though there are some concepts that I'll probably ask you guys about what should/shouldnt be kept within.


If you wish to elaborate the subject/solutions I'd more than happy.
 
Last edited:
Level 26
Joined
Mar 19, 2008
Messages
3,140
Updated to 1.0.1.0 yet immidiately after I've found even better solution to my problem thus here we got 1.0.2.0.

When reading one of TriggerHappy's post I realised that I've cimpletely forgotten about implement optional for modules. In such case we can greatly improve the interaction between user and the interface:

Initialy:
JASS:
struct qsort_struct extends array
//! runtextmacro DEFINE_QSORT("integer", "false")
endstruct

struct myStringSort extends array

    static method compare_string takes string a, string b returns integer
        if ( a == b ) then
            return 0
        elseif ( StringLength(a) < StringLength(b) ) then
            return -1
        else // ( a > b )
            return 1
        endif
    endmethod

    //! runtextmacro DEFINE_QSORT("string", "false")

endstruct
To this:
JASS:
//! runtextmacro DEFINE_QSORT("IntegerSort", "integer", "false", "")

module CompareStrings
    static method compare_string takes string a, string b returns integer
        if ( a == b ) then
            return 0
        elseif ( StringLength(a) < StringLength(b) ) then
            return -1
        else // ( a > b )
            return 1
        endif
    endmethod
endmodule

//! runtextmacro DEFINE_QSORT("StringSort", "string", "false", "CompareStrings")
And eventually:
JASS:
//! runtextmacro DEFINE_QSORT("IntegerSort", "integer", "false", "")

module CompareStrings
    // notice that "_$TYPE$" for compare method is no longer needed
    static method compare takes string a, string b returns integer
        if ( a == b ) then
            return 0
        elseif ( StringLength(a) < StringLength(b) ) then
            return -1
        else // ( a > b )
            return 1
        endif
    endmethod
endmodule

//! runtextmacro DEFINE_QSORT("StringSort", "string", "false", "CompareStrings")
JASS:
    private static constant boolean DEFINED_QSORT_$TYPE$ = true

    static if not thistype.compare.exists then

        static if thistype.DEFINED_QSORT_integer then
            private static method compare_integer takes integer a, integer b returns integer
                if ( a < b ) then
                    return -1
                elseif ( a == b ) then
                    return 0
                else // ( a > b )
                    return 1
                endif
            endmethod
        elseif thistype.DEFINED_QSORT_real then
            private static method compare_real takes real a, real b returns integer
                if ( a < b ) then
                    return -1
                elseif ( a == b ) then
                    return 0
                else // ( a > b )
                    return 1
                endif
            endmethod
        endif

    endif
To this:
JASS:
// Default integer & real comparators
//! textmacro_once DEFINE_DEFAULT_COMPARATOR takes TYPE
library_once $TYPE$DefaultComparator
    module $TYPE$DefaultComparator
        static method compare takes $TYPE$ a, $TYPE$ b returns integer
            if ( a < b ) then
                return -1
            elseif ( a == b ) then
                return 0
            else // ( a > b )
                return 1
            endif
        endmethod
    endmodule
endlibrary
//! endtextmacro

//! runtextmacro DEFINE_DEFAULT_COMPARATOR("integer")
//! runtextmacro DEFINE_DEFAULT_COMPARATOR("real")

// (...)

    implement optional $COMPARATOR$
    static if not thistype.compare.exists then
        static if LIBRARY_$TYPE$DefaultComparator then
            implement optional $TYPE$DefaultComparator
        endif
    endif
This method, however, increases argument count for DEFINE_QSORT macro from 2 to 4 (also a NAME has been added), yet in my opinion it is worth the cost. User can always provide "" to ignore the sort interface, what in this case is rather stupid considering you just implemented an empty library which you probably wanted to use for sorting a set type of data.

Method compare within such module must not be private.

Macro as of now declares library with name given by $NAME$ that contains structs with sorting api.
 
Level 26
Joined
Mar 19, 2008
Messages
3,140
Let's say I'd like to resignate from Table being a requirement here. In place of table instance there would have to be some kind of an array or a list.

I could also replace struct with module and provide generic <type> array for user to work with it prior to calling sort function. Sorts would operate on such array directly. It's a bit uncomfortable for user tho..

There isn't much of a choice here since we are a bit restricted due to jass limits. However, I'd like to hear which method is better. Without a Table, usage of array or list structure might be wierd, it doesn't really fix the problem.

My goal was to create global struct type for sorts, yet without pointers in jass, without ability to pass an array as an argument, available possibilities are limited.
 
Top