PureBasic's built-in sorting routines for arrays and lists are limited.
They cannot be used e.g. for
- sorting lists/arrays of strings by the length of the strings
- sorting lists/arrays of strings according to self-defined rules (there are different rules for different languages, especially with regard to special characters)
- sorting lists/arrays of strings that contain numbers numerically (instead, they are always sorted alphabetically like other strings, so that e.g. 11 goes before 7)
- sorting lists/arrays of structures by more than one field
- sorting lists/arrays of arrays
- sorting lists/arrays of pointers according to the content of their targets
Custom sort for arrays can be done with a trick that wilbert has demonstrated.
The module
A stable hybrid sorting algorithm is used here: Longer lists are sorted by the fastest recursive PureBasic implementation of MergeSort that I'm aware of, shorter lists are sorted by an efficient InsertionSort implementation. The used threshold between "long" and "short" can be changed at runtime. A small program for testing the speed when using different thresholds is in the 3rd post here. No data are copied at all, all sorting is done by manipulating pointers.
This module contains individual procedures for sorting lists of integers, doubles, strings, and pointers.
A problem arises with structures: PureBasic doesn't allow us to write generic procedures that work for all data types, and a module like this can't contain separate procedures for all possible structures. So the module provides the public macro SortListAny(), which internally creates a list of pointers to the structures, then sorts the pointer list and finally rearranges the original structure list according to the order of the pointers. This is still considerably faster than the in-place version of MergeSort which this module used up to version 1.10.
// edit 2019-03-03:
In the meantime, wilbert has posted nifty code that does non-recursive custom sort for Linked Lists, which is even faster than my following code.
Code: Select all
; -- Custom sort for linked lists
; <https://www.purebasic.fr/english/viewtopic.php?f=12&t=71677>
; Version 1.22, 2019-01-31
; Purebasic 5.20 LTS or newer is required because of the module.
; Cross-platform, x86 and x64, Unicode compliant.
; This module uses a hybrid sorting algorithm. For lists of a
; size bigger than the internal threshold (value of variable
; 'sInsertionSortMaxSize'), it uses MergeSort, for smaller lists
; (including those that are always produced by MergeSort as
; intermediate results), InsertionSort is used. This is
; considerably faster than using MergeSort alone. By calling
; SetInsertionSortMaxSize(), your code can change the threshold
; at runtime.
; Your code must provide a custom comparison callback function
; of type 'ProtoCompare' (see the beginning of the module, and
; examples in the demo code). If the comparison function is
; written properly, then sorting by the procedures of this
; module is stable.
; ------------------------------------------------------------------------------
; MIT License
;
; Copyright (c) 2018-2019 Jürgen Lüthje <http://luethje.eu/>
;
; Permission is hereby granted, free of charge, to any person obtaining a copy
; of this software and associated documentation files (the "Software"), to deal
; in the Software without restriction, including without limitation the rights
; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
; copies of the Software, and to permit persons to whom the Software is
; furnished to do so, subject to the following conditions:
;
; The above copyright notice and this permission notice shall be included in all
; copies or substantial portions of the Software.
;
; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
; SOFTWARE.
; ------------------------------------------------------------------------------
DeclareModule CS
EnableExplicit
Declare.i SetInsertionSortMaxSize (insSortMaxSize.i)
Declare SortListI (List a.i(), *Compare, mode.i=#PB_Sort_Ascending, first.i=0, last.i=-1)
Declare SortListD (List a.d(), *Compare, mode.i=#PB_Sort_Ascending, first.i=0, last.i=-1)
Declare SortListS (List a.s(), *Compare, mode.i=#PB_Sort_Ascending, first.i=0, last.i=-1)
Declare SortListPtr (List *a(), *Compare, mode.i=#PB_Sort_Ascending, first.i=0, last.i=-1)
Macro RearrangeData (_dataList_, _pointerList_)
; -- rearrange the elements of _dataList_ according to the order of elements in _pointerList_
ForEach _pointerList_
ChangeCurrentElement(_dataList_, _pointerList_)
MoveElement(_dataList_, #PB_List_Last)
Next
EndMacro
NewList *ptr() ; * for internal use by Macro SortListAny() only *
Macro SortListAny (_list_, _compare_, _mode_=#PB_Sort_Ascending, _first_=0, _last_=-1)
; -- sort any kind of list (indirectly by pointers)
ForEach _list_
AddElement(CS::*ptr())
CS::*ptr() = @ _list_
Next
CS::SortListPtr(CS::*ptr(), _compare_, _mode_, _first_, _last_)
CS::RearrangeData(_list_, CS::*ptr())
ClearList(CS::*ptr())
EndMacro
EndDeclareModule
Module CS
Prototype.i ProtoCompare (*a, *b, mode.i)
Define sInsertionSortMaxSize.i = 40
Procedure.i SetInsertionSortMaxSize (insSortMaxSize.i)
; -- For maximum sorting speed, you might need to change the value of the
; internal variable 'sInsertionSortMaxSize'.
; in : insSortMaxSize: new value of the internal variable 'sInsertionSortMaxSize'
; out: return value : old value of the internal variable 'sInsertionSortMaxSize',
; or 0 on error
Shared sInsertionSortMaxSize
If insSortMaxSize >= 1
Swap sInsertionSortMaxSize, insSortMaxSize
ProcedureReturn insSortMaxSize
Else
ProcedureReturn 0
EndIf
EndProcedure
;------------------------------------------------------------------------------------------------------
Macro _Merge (_listA_, _listB_)
; -- merge two sorted linked lists
; in : _listA_, _listB_: partial lists which are already sorted (both lists are not empty)
; out: _listA_ : sorted list that contains all elements of _listA_ and _listB_
; -- merge both partial lists
*curA = LastElement(_listA_)
MergeLists(_listB_, _listA_) ; Append _listB_ to the end of _listA_.
*curB = NextElement(_listA_)
If IsInWrongOrder(*curA, *curB, SortMode) > 0
countA = 0
*curA = FirstElement(_listA_)
; -- rearrange the elements in the merged list
While countA < firstB And *curB <> #Null
If IsInWrongOrder(*curA, *curB, SortMode) > 0
ChangeCurrentElement(_listA_, *curB)
*nxtB = NextElement(_listA_)
ChangeCurrentElement(_listA_, *curB)
MoveElement(_listA_, #PB_List_Before, *curA)
*curB = *nxtB
Else
countA + 1
EndIf
*curA = NextElement(_listA_) ; *curA always points to the first element which is not yet sorted.
Wend
EndIf
EndMacro
Macro _InsertionSort (_list_)
FirstElement(_list_)
While NextElement(_list_) <> #Null
*curElement = @ _list_ ; save pointer to current element
*target = *curElement
*lastSortedElement = PreviousElement(_list_)
*prevElement = *lastSortedElement
While *prevElement <> #Null And IsInWrongOrder(*prevElement, *curElement, SortMode) > 0
*target = *prevElement
*prevElement = PreviousElement(_list_)
Wend
ChangeCurrentElement(_list_, *curElement)
If *target <> *curElement
MoveElement(_list_, #PB_List_Before, *target)
ChangeCurrentElement(_list_, *lastSortedElement)
EndIf
Wend
EndMacro
Macro _SortRange (_ProcSuffix_, _list_)
; -- select the part of the list that needs to be sorted
If first = 0
If last = -1 Or last = ListSize(_list_) - 1
; -- Sort all elements
_Sort#_ProcSuffix_(_list_)
ElseIf 0 < last And last < ListSize(_list_) - 1
; -- Sort leading part of the list
SelectElement(_list_, last)
SplitList(_list_, t(), #True) ; Move the trailing part of _list_ to t().
_Sort#_ProcSuffix_(_list_) ; Sort rest of _list_.
MergeLists(t(), _list_) ; Move the saved trailing part back to _list_.
EndIf
ElseIf 0 < first And first < ListSize(_list_) - 1
If last = -1 Or last = ListSize(_list_) - 1
; -- Sort trailing part of the list
SelectElement(_list_, first)
SplitList(_list_, t()) ; Move the trailing part of _list_ to t().
_Sort#_ProcSuffix_(t()) ; Sort t().
MergeLists(t(), _list_) ; Move the sorted trailing part back to _list_.
ElseIf 0 < last And last < ListSize(_list_) - 1
; -- Sort middle part of the list
SelectElement(_list_, last)
SplitList(_list_, t(), #True) ; Move the trailing part of _list_ to t().
SelectElement(_list_, first)
SplitList(_list_, m()) ; Move the middle part of _list_ to m().
_Sort#_ProcSuffix_(m()) ; Sort m().
MergeLists(m(), _list_) ; Move the sorted middle part back to _list_.
MergeLists(t(), _list_) ; Move the saved trailing part back to _list_.
EndIf
EndIf
EndMacro
Global IsInWrongOrder.ProtoCompare
Global SortMode.i
;------------------------------------------------------------------------------------------------------
Procedure _SortI (List a.i())
Shared sInsertionSortMaxSize
Protected *target
Protected *curA, *curB, *nxtB, countA, firstB ; for _Merge()
Protected *curElement, *lastSortedElement, *prevElement ; for _InsertionSort()
Protected NewList b.i()
If ListSize(a()) <= 1
ProcedureReturn
EndIf
If ListSize(a()) > sInsertionSortMaxSize
firstB = Int(ListSize(a()) / 2)
SelectElement(a(), firstB)
SplitList(a(), b()) ; Move the second half of a() to b().
_SortI(a())
_SortI(b())
_Merge(a(), b())
Else
_InsertionSort(a())
EndIf
EndProcedure
Procedure SortListI (List a.i(), *Compare, mode.i=#PB_Sort_Ascending, first.i=0, last.i=-1)
; -- Sort list a() according to the given comparison function
; in : a() : List of integers to be sorted
; *Compare: address of a custom comparison function of type 'ProtoCompare'
; mode : This value is just passed to the custom comparison function.
; first : index of first element to sort (default: sort ... )
; last : index of last element to sort (... the whole list)
; out: a() : completely or partly sorted list
Protected NewList m.i()
Protected NewList t.i()
IsInWrongOrder = *Compare
SortMode = mode
_SortRange(I, a())
EndProcedure
;------------------------------------------------------------------------------------------------------
Procedure _SortD (List a.d())
Shared sInsertionSortMaxSize
Protected *target
Protected *curA, *curB, *nxtB, countA, firstB ; for _Merge()
Protected *curElement, *lastSortedElement, *prevElement ; for _InsertionSort()
Protected NewList b.d()
If ListSize(a()) <= 1
ProcedureReturn
EndIf
If ListSize(a()) > sInsertionSortMaxSize
firstB = Int(ListSize(a()) / 2)
SelectElement(a(), firstB)
SplitList(a(), b()) ; Move the second half of a() to b().
_SortD(a())
_SortD(b())
_Merge(a(), b())
Else
_InsertionSort(a())
EndIf
EndProcedure
Procedure SortListD (List a.d(), *Compare, mode.i=#PB_Sort_Ascending, first.i=0, last.i=-1)
; -- Sort list a() according to the given comparison function
; in : a() : List of doubles to be sorted
; *Compare: address of a custom comparison function of type 'ProtoCompare'
; mode : This value is just passed to the custom comparison function.
; first : index of first element to sort (default: sort ... )
; last : index of last element to sort (... the whole list)
; out: a() : completely or partly sorted list
Protected NewList m.d()
Protected NewList t.d()
IsInWrongOrder = *Compare
SortMode = mode
_SortRange(D, a())
EndProcedure
;------------------------------------------------------------------------------------------------------
Procedure _SortS (List a.s())
Shared sInsertionSortMaxSize
Protected *target
Protected *curA, *curB, *nxtB, countA, firstB ; for _Merge()
Protected *curElement, *lastSortedElement, *prevElement ; for _InsertionSort()
Protected NewList b.s()
If ListSize(a()) <= 1
ProcedureReturn
EndIf
If ListSize(a()) > sInsertionSortMaxSize
firstB = Int(ListSize(a()) / 2)
SelectElement(a(), firstB)
SplitList(a(), b()) ; Move the second half of a() to b().
_SortS(a())
_SortS(b())
_Merge(a(), b())
Else
_InsertionSort(a())
EndIf
EndProcedure
Procedure SortListS (List a.s(), *Compare, mode.i=#PB_Sort_Ascending, first.i=0, last.i=-1)
; -- Sort list a() according to the given comparison function
; in : a() : List of strings to be sorted
; *Compare: address of a custom comparison function of type 'ProtoCompare'
; mode : This value is just passed to the custom comparison function.
; first : index of first element to sort (default: sort ... )
; last : index of last element to sort (... the whole list)
; out: a() : completely or partly sorted list
Protected NewList m.s()
Protected NewList t.s()
IsInWrongOrder = *Compare
SortMode = mode
_SortRange(S, a())
EndProcedure
;------------------------------------------------------------------------------------------------------
Procedure _SortPtr (List *a())
Shared sInsertionSortMaxSize
Protected *target
Protected *curA, *curB, *nxtB, countA, firstB ; for _Merge()
Protected *curElement, *lastSortedElement, *prevElement ; for _InsertionSort()
Protected NewList *b()
If ListSize(*a()) <= 1
ProcedureReturn
EndIf
If ListSize(*a()) > sInsertionSortMaxSize
firstB = Int(ListSize(*a()) / 2)
SelectElement(*a(), firstB)
SplitList(*a(), *b()) ; Move the second half of *a() to *b().
_SortPtr(*a())
_SortPtr(*b())
_Merge(*a(), *b())
Else
_InsertionSort(*a())
EndIf
EndProcedure
Procedure SortListPtr (List *a(), *Compare, mode.i=#PB_Sort_Ascending, first.i=0, last.i=-1)
; -- Sort list *a() according to the given comparison function
; in : *a() : List of pointers to be sorted according to their targets
; *Compare: address of a custom comparison function of type 'ProtoCompare'
; mode : This value is just passed to the custom comparison function.
; first : index of first element to sort (default: sort ... )
; last : index of last element to sort (... the whole list)
; out: *a() : completely or partly sorted list
Protected NewList m.i()
Protected NewList t.i()
IsInWrongOrder = *Compare
SortMode = mode
_SortRange(Ptr, *a())
EndProcedure
;-=====================================================================================================
EndModule
CompilerIf #PB_Compiler_IsMainFile
; -- Module demo
CompilerIf #PB_Compiler_Debugger = #False
MessageRequester("Error",
"Switch Debugger on to see the demo.",
#PB_MessageRequester_Error)
End
CompilerEndIf
EnableExplicit
Debug "Old value of 'sInsertionSortMaxSize': " + CS::SetInsertionSortMaxSize(3)
Debug ""
Define first, last, k, prefix$
Macro ShowList (_list_, _first_=99, _last_=99)
Debug ""
k = 0
ForEach _list_
If _first_ <= k And k <= _last_
prefix$ = "> "
Else
prefix$ = " "
EndIf
Debug Str(k) + ") " + prefix$ + _list_
k + 1
Next
EndMacro
;================================================================
Procedure.i CompareI (*a.Integer, *b.Integer, mode.i)
; -- custom comparison function of type 'ProtoCompare' (see Module CS)
; in : *a, *b: pointers to integers to be compared
; mode : mode of comparison:
; #PB_Sort_Ascending/#PB_Sort_Descending
; out: return value: < 0, 0, or > 0
If (mode & #PB_Sort_Descending)
ProcedureReturn *b\i - *a\i
Else
ProcedureReturn *a\i - *b\i
EndIf
EndProcedure
NewList i.i()
For k = 0 To 6
AddElement(i())
i() = k
Next
Debug "Integers shuffled:"
RandomizeList(i())
ShowList(i())
Debug "-----------------------------------------"
Debug "Whole integer list sorted ascending:"
CS::SortListI(i(), @ CompareI(), #PB_Sort_Ascending)
ShowList(i(), 0, 6)
Debug "-----------------------------------------"
Debug "Whole integer list sorted descending:"
CS::SortListI(i(), @ CompareI(), #PB_Sort_Descending)
ShowList(i(), 0, 6)
Debug ""
Debug "========================================="
Debug ""
Procedure.i CompareD (*a.Double, *b.Double, mode.i)
; -- custom comparison function of type 'ProtoCompare' (see Module CS)
; in : *a, *b: pointers to doubles to be compared
; mode : mode of comparison:
; #PB_Sort_Ascending/#PB_Sort_Descending
; out: return value: #True/#False
If (mode & #PB_Sort_Descending)
ProcedureReturn Bool(*a\d < *b\d)
Else
ProcedureReturn Bool(*a\d > *b\d)
EndIf
EndProcedure
NewList d.d()
For k = 0 To 6
AddElement(d())
d() = k * 0.4
Next
Debug "Doubles shuffled:"
RandomizeList(d())
ShowList(d())
first = 2
Debug "-----------------------------------------"
Debug "Doubles from #" + first + " to last element sorted ascending:"
CS::SortListD(d(), @ CompareD(), #PB_Sort_Ascending, first)
ShowList(d(), first)
Debug "-----------------------------------------"
Debug "Doubles from #" + first + " to last element sorted descending:"
CS::SortListD(d(), @ CompareD(), #PB_Sort_Descending, first)
ShowList(d(), first)
Debug ""
Debug "========================================="
Debug ""
Procedure.i CompareS (*a.String, *b.String, mode.i)
; -- custom comparison function of type 'ProtoCompare' (see Module CS)
; in : *a, *b: pointers to strings to be compared
; mode : mode of comparison:
; #PB_Sort_Ascending/#PB_Sort_Descending/#PB_Sort_NoCase
; out: return value: < 0, 0, or > 0
Protected ret.i=0
If (mode & #PB_Sort_NoCase)
ret = CompareMemoryString(@ *a\s, @ *b\s, #PB_String_NoCase)
Else
ret = CompareMemoryString(@ *a\s, @ *b\s)
EndIf
If (mode & #PB_Sort_Descending)
ProcedureReturn -ret
Else
ProcedureReturn ret
EndIf
EndProcedure
DataSection
StringData:
Data.s "aaaaaaaaaa"
Data.s "bbbbbbbbbb"
Data.s "cccccccc"
Data.s "dddddddd"
Data.s "eeeeee"
Data.s "ffffff"
Data.s "gggggg"
Data.s ""
EndDataSection
Procedure ReadStrings (List s.s())
Protected temp$
Restore StringData
Read.s temp$
While temp$
AddElement(s())
s() = temp$
Read.s temp$
Wend
EndProcedure
NewList s.s()
ReadStrings(s())
Debug "Strings shuffled:"
RandomizeList(s())
ShowList(s())
first = 0 : last = 5
Debug "-----------------------------------------"
Debug "Strings from #" + first + " to #" + last + " sorted ascending:"
CS::SortListS(s(), @ CompareS(), #PB_Sort_Ascending, first, last)
ShowList(s(), first, last)
Debug "-----------------------------------------"
Debug "Strings from #" + first + " to #" + last + " sorted descending:"
CS::SortListS(s(), @ CompareS(), #PB_Sort_Descending, first, last)
ShowList(s(), first, last)
Debug ""
Debug "========================================="
Debug ""
Structure Person
FamilyName.s
GivenName.s
Age.i
EndStructure
Enumeration 0 Step 4
#ByFamilyName
#ByGivenName
#ByAge
EndEnumeration
Procedure.i ComparePtr (*pa.Integer, *pb.Integer, mode.i)
; -- custom comparison function of type 'ProtoCompare' (see Module CS)
; in : *pa, *pb: pointers to pointers to data to be compared
; mode : mode of comparison:
; number for structure field that is used for comparison, combined
; with #PB_Sort_Ascending/#PB_Sort_Descending/#PB_Sort_NoCase
; out: return value: #True/#False
;
; CAVE! When using '<=' instead of '<' or '>=' instead of '>' here,
; sorting will work but won't be stable!
Protected.Person *a = *pa\i, *b = *pb\i ; dereference the pointers passed as parameters
Select mode
; -- by family name
Case 0 ; ascending
ProcedureReturn Bool(*a\FamilyName > *b\FamilyName)
Case 1 ; descending
ProcedureReturn Bool(*a\FamilyName < *b\FamilyName)
Case 2 ; ascending and case-insensitive
ProcedureReturn Bool(UCase(*a\FamilyName) > UCase(*b\FamilyName))
Case 3 ; descending and case-insensitive
ProcedureReturn Bool(UCase(*a\FamilyName) < UCase(*b\FamilyName))
; -- by given name
Case 4 ; ascending
ProcedureReturn Bool(*a\GivenName > *b\GivenName)
Case 5 ; descending
ProcedureReturn Bool(*a\GivenName < *b\GivenName)
Case 6 ; ascending and case-insensitive
ProcedureReturn Bool(UCase(*a\GivenName) > UCase(*b\GivenName))
Case 7 ; descending and case-insensitive
ProcedureReturn Bool(UCase(*a\GivenName) < UCase(*b\GivenName))
; -- by age
Case 8 ; ascending
ProcedureReturn Bool(*a\Age > *b\Age)
Case 9 ; descending
ProcedureReturn Bool(*a\Age < *b\Age)
EndSelect
EndProcedure
DataSection
People:
Data.s "Schmidt", "Michelle"
Data.i 50
Data.s "Johnson", "Jamie"
Data.i 40
Data.s "Schmidt", "Jürgen"
Data.i 30
Data.s "Miller", "Mary"
Data.i 40
Data.s "Parker", "Peter"
Data.i 20
Data.s "Johnson", "Karl"
Data.i 30
Data.s "Parker", "Michelle"
Data.i 60
Data.s ""
EndDataSection
Procedure GetData (List x.Person())
Protected temp$
Restore People
Read.s temp$
While temp$
AddElement(x())
x()\FamilyName = temp$
Read.s x()\GivenName
Read.i x()\Age
Read.s temp$
Wend
EndProcedure
Procedure ShowData (List x.Person(), first.i=99, last.i=99)
Protected k.i, prefix$
Debug ""
k = 0
ForEach x()
If first <= k And k <= last
prefix$ = "> "
Else
prefix$ = " "
EndIf
With x()
Debug Str(k) + ") " + prefix$ + LSet(\FamilyName + ", " + \GivenName + ", ", 20) + \Age
EndWith
k + 1
Next
EndProcedure
Procedure ShowTargets (List *p(), first.i=99, last.i=99)
Protected k.i, prefix$, *temp.Person
Debug ""
k = 0
ForEach *p()
If first <= k And k <= last
prefix$ = "> "
Else
prefix$ = " "
EndIf
*temp = *p()
With *temp
Debug Str(k) + ") " + prefix$ + LSet(\FamilyName + ", " + \GivenName + ", ", 20) + \Age
EndWith
k + 1
Next
EndProcedure
NewList x.Person()
NewList *p()
GetData(x())
RandomizeList(x())
ForEach x()
AddElement(*p())
*p() = @ x()
Next
Debug "Pointers to shuffled data:"
ShowTargets(*p())
first = 1 : last = 5
Debug "-----------------------------------------"
Debug "Pointers from #" + first + " to #" + last + " sorted ascending by family name (stable):"
CS::SortListPtr(*p(), @ ComparePtr(), #PB_Sort_Ascending|#ByFamilyName, first, last)
ShowTargets(*p(), first, last)
Debug "-----------------------------------------"
Debug "Pointers from #" + first + " to #" + last + " sorted descending by age (stable):"
CS::SortListPtr(*p(), @ ComparePtr(), #PB_Sort_Descending|#ByAge, first, last)
ShowTargets(*p(), first, last)
Debug "-----------------------------------------"
Debug "The original data are still unsorted:"
ShowData(x())
Debug "-----------------------------------------"
Debug "Now the original data are sorted, too"
Debug "(according to the current order of the pointers):"
CS::RearrangeData(x(), *p())
ShowData(x(), first, last)
Debug ""
Debug "========================================="
Debug ""
Structure Country
Name.s
Capital.s
Population.i
EndStructure
Enumeration 0 Step 4
#ByName
#ByCapital
#ByPopulation
EndEnumeration
Procedure.i CompareStruc (*pa.Integer, *pb.Integer, mode.i)
; -- custom comparison function of type 'ProtoCompare'
; in : *pa, *pb: pointers to pointers to structures to be compared
; mode : mode of comparison:
; number for structure field that is used for comparison, combined
; with #PB_Sort_Ascending/#PB_Sort_Descending/#PB_Sort_NoCase
; out: return value: #True/#False
;
; CAVE! When using '<=' instead of '<' or '>=' instead of '>' here,
; sorting will work but won't be stable!
Protected.Country *a = *pa\i, *b = *pb\i ; dereference the pointers passed as parameters
Select mode
; -- by name
Case 0 ; ascending
ProcedureReturn Bool(*a\Name > *b\Name)
Case 1 ; descending
ProcedureReturn Bool(*a\Name < *b\Name)
Case 2 ; ascending and case-insensitive
ProcedureReturn Bool(UCase(*a\Name) > UCase(*b\Name))
Case 3 ; descending and case-insensitive
ProcedureReturn Bool(UCase(*a\Name) < UCase(*b\Name))
; -- by capital
Case 4 ; ascending
ProcedureReturn Bool(*a\Capital > *b\Capital)
Case 5 ; descending
ProcedureReturn Bool(*a\Capital < *b\Capital)
Case 6 ; ascending and case-insensitive
ProcedureReturn Bool(UCase(*a\Capital) > UCase(*b\Capital))
Case 7 ; descending and case-insensitive
ProcedureReturn Bool(UCase(*a\Capital) < UCase(*b\Capital))
; -- by population
Case 8 ; ascending
ProcedureReturn Bool(*a\Population > *b\Population)
Case 9 ; descending
ProcedureReturn Bool(*a\Population < *b\Population)
EndSelect
EndProcedure
DataSection
Countries:
Data.s "France", "Paris"
Data.i 67348000
Data.s "England", "London"
Data.i 55619400
Data.s "Japan", "Tokyo"
Data.i 126440000
Data.s "New Zealand", "Wellington"
Data.i 4915240
Data.s "Germany", "Berlin"
Data.i 82800000
Data.s "Canada", "Ottawa"
Data.i 37067011
Data.s "China", "Beijing"
Data.i 1403500365
Data.s ""
EndDataSection
Procedure ReadStruc (List x.Country())
Protected temp$
Restore Countries
Read.s temp$
While temp$
AddElement(x())
x()\Name = temp$
Read.s x()\Capital
Read.i x()\Population
Read.s temp$
Wend
EndProcedure
Procedure ShowStruc (List r.Country(), first.i=99, last.i=99)
Protected k.i, prefix$
Debug ""
k = 0
ForEach r()
If first <= k And k <= last
prefix$ = "> "
Else
prefix$ = " "
EndIf
With r()
Debug Str(k) + ") " + prefix$ + LSet(\Name + ", " + \Capital + ", ", 26) + RSet(Str(\Population), 10)
EndWith
k + 1
Next
EndProcedure
NewList r.Country()
ReadStruc(r())
RandomizeList(r())
Debug "Shuffled structured list:"
ShowStruc(r())
Debug "-----------------------------------------"
Debug "Whole structured list sorted ascending by name:"
CS::SortListAny(r(), @ CompareStruc(), #PB_Sort_Ascending|#ByName)
ShowStruc(r(), 0, 6)
Debug "-----------------------------------------"
Debug "Whole structured list sorted descending by population:"
CS::SortListAny(r(), @ CompareStruc(), #PB_Sort_Descending|#ByPopulation)
ShowStruc(r(), 0, 6)
CompilerEndIf