when it comes to sorting of arrays, it sometimes makes sense not to sort the actual data in the array, but to sort the array indexes instead.
This is desirable e.g.
- when the array elements are big, so that moving them around would take much time.
- when for some reason you need to access the array elements in the original order and in sorted order.
a(i) is still the original value at position i, a(idx(i)) is the value at position i after sorting. - when you want to get the ranks of the array elements, without changing their order,
- when you need to sort 2 or more "parallel" arrays.
Say some 2-dimensional points are given in the form of 2 separate arrays x() and y(). These are called "parallel" arrays, because for any allowed index i, x(i) and y(i) strictly belong together. When e.g. the points are going to be sorted according their x values, the y values have to be rearranged exactly in the same way, so that after sorting, all corresponding x and y values still have the same index.
The first group consists of basic procedures for sorting array indexes, and for rearranging array elements according to the order of the indexes. Only procedures for variable types .i, .d, and .s are provided here. If needed, you can easily add procedures for other data types yourself. The actual code is contained in three macros anyway, and the procedures are only for providing an appropriate public interface. The MergeSort algorithm is used here, which is fast and stable.
The second group consists of some procedures for sorting parallel arrays. They are just "wrappers" of the basic procedures. It will be easy to write any other wrapper according to your needs. E.g. it is no problem to sort not only two, but any other number of parallel arrays.
The third group consists of procedures for calculating the ranks of all array elements. These procedures also utilise the basic procedures from the first group. Ranking is important for several statistical calculations.
Have fun!
Code: Select all
; <http://www.purebasic.fr/english/viewtopic.php?t=60013>
; Version 1.00, 2023-08-12
; successfully tested with
; [v] PB 5.72 LTS and PB 6.02 LTS (x64) on Windows 11 – both ASM and C backend
; [v] PB 6.03 beta 4 (x64) on Linux Mint 20.3 – both ASM and C backend
DeclareModule SortAI
; -- basic procedures
Declare.i SortArrayIndexesI (Array a.i(1), Array idx.i(1), mode.i, first.i=0, last.i=-1)
Declare.i RearrangeArrayI (Array idx.i(1), Array a.i(1), first.i=0, last.i=-1)
Declare.i SortArrayIndexesD (Array a.d(1), Array idx.i(1), mode.i, first.i=0, last.i=-1)
Declare.i RearrangeArrayD (Array idx.i(1), Array a.d(1), first.i=0, last.i=-1)
Declare.i SortArrayIndexesS (Array a.s(1), Array idx.i(1), mode.i, first.i=0, last.i=-1)
Declare.i RearrangeArrayS (Array idx.i(1), Array a.s(1), first.i=0, last.i=-1)
; -- some "wrappers" of the basic procedures
Declare.i SortParallelArraysII (Array a.i(1), Array b.i(1), mode.i, first.i=0, last.i=-1)
Declare.i SortParallelArraysID (Array a.i(1), Array b.d(1), mode.i, first.i=0, last.i=-1)
Declare.i SortParallelArraysIS (Array a.i(1), Array b.s(1), mode.i, first.i=0, last.i=-1)
Declare.i SortParallelArraysDI (Array a.d(1), Array b.i(1), mode.i, first.i=0, last.i=-1)
Declare.i SortParallelArraysDD (Array a.d(1), Array b.d(1), mode.i, first.i=0, last.i=-1)
Declare.i SortParallelArraysDS (Array a.d(1), Array b.s(1), mode.i, first.i=0, last.i=-1)
Declare.i SortParallelArraysSI (Array a.s(1), Array b.i(1), mode.i, first.i=0, last.i=-1)
Declare.i SortParallelArraysSD (Array a.s(1), Array b.d(1), mode.i, first.i=0, last.i=-1)
Declare.i SortParallelArraysSS (Array a.s(1), Array b.s(1), mode.i, first.i=0, last.i=-1)
; -- ranking procedures
Declare.i RankI (Array a.i(1), Array r.d(1))
Declare.i RankD (Array a.d(1), Array r.d(1))
Declare.i RankS (Array a.s(1), Array r.d(1))
EndDeclareModule
Module SortAI
EnableExplicit
Macro Merge (_case_)
; in: _case_: nothing or 'LCase'
; copy first half of idx() to auxiliary array h()
i = 0
j = lo
While j <= m
h(i) = idx(j)
i + 1
j + 1
Wend
; copy next biggest (or smallest, respectively) element back to idx()
i = 0
k = lo
While k < j And j <= hi
If mode & #PB_Sort_Descending = 1
flag = Bool(_case_(a(h(i))) >= _case_(a(idx(j))))
Else
flag = Bool(_case_(a(h(i))) <= _case_(a(idx(j))))
EndIf
If flag
idx(k) = h(i)
i + 1
Else
idx(k) = idx(j)
j + 1
EndIf
k + 1
Wend
; copy rest of h() (if existing) back to idx()
While k < j
idx(k) = h(i)
k + 1
i + 1
Wend
EndMacro
Macro MergeSort (_case_=)
; -- efficient iterative Mergesort
; in: _case_: nothing or 'LCase'
; [after
; Lang, H. W.: Algorithmen.
; Oldenbourg, 2nd ed. 2006, pp. 32-33
; (German)]
If first < 0
ProcedureReturn #False ; error
ElseIf last = -1
last = ArraySize(a())
ElseIf last > ArraySize(a())
ProcedureReturn #False ; error
EndIf
If first > last
ProcedureReturn #False ; error
EndIf
n = last + 1 - first
Dim h.i(n/2) ; create an auxiliary array
; initialize array of indexes
Dim idx.i(ArraySize(a()))
For r = 0 To ArraySize(a())
idx(r) = r
Next
s = 1 ; 's' is the length of the second half (1,2,4,8,...)
While s < n
m = last - s ; 'm' points to the last element of the first half
While m >= first
lo = m - s + 1 ; 'lo' points to the first element of the first half
If lo < first
lo = first
EndIf
hi = m + s ; 'hi' points to the last element of the second half
Merge(_case_)
m - (s + s)
Wend
s + s
Wend
ProcedureReturn #True ; success
EndMacro
Macro Rearrange
Protected.i i, j, k
Protected Dim h.i(0)
If first < 0
ProcedureReturn #False ; error
ElseIf last = -1
last = ArraySize(a())
ElseIf last > ArraySize(a())
ProcedureReturn #False ; error
EndIf
If first > last
ProcedureReturn #False ; error
ElseIf last > ArraySize(idx())
ProcedureReturn #False ; error
EndIf
CopyArray(idx(), h()) ; use a copy of the index array,
; to leave the index array as is
For i = 0 To last
If h(i) <> i
t = a(i)
k = i
Repeat
j = k
a(j) = a(h(j))
k = h(j)
h(j) = j
Until k = i
a(j) = t
EndIf
Next
ProcedureReturn #True ; success
EndMacro
;--------------------------------------------------------------------
Procedure.i SortArrayIndexesI (Array a.i(1), Array idx.i(1), mode.i, first.i=0, last.i=-1)
; -- sort the elements of idx(), according to array a()
; in : a() : array with the elements which are to be compared
; idx(): index array which is to be sorted
; mode : #PB_Sort_Ascending / #PB_Sort_Descending
; first: first element to sort
; last : last element to sort
; out: idx(): index array, completely or partly sorted:
; idx(first) is the index of the smallest (biggest) element in (the sorted part of) a(),
; idx(first+1) is the index of the second smallest (biggest) element in (the sorted part of) a(),
; etc.
; return value: #True on success, #False on error
Protected.i i, j, k, flag
Protected.i m, n, r, s, lo, hi
Protected Dim h.i(0)
MergeSort()
EndProcedure
Procedure.i RearrangeArrayI (Array idx.i(1), Array a.i(1), first.i=0, last.i=-1)
; -- rearrange the elements of array a() in place, according to idx()
; in : idx(): idx(first) is the index of the smallest (biggest) element in (the sorted part of) a(),
; idx(first+1) is the index of the second smallest (biggest) element in (the sorted part of) a(),
; etc.
; a() : array that is to be rearranged
; first: first element taken into account
; last : last element taken into account
; out: a(): rearranged array
; (idx() remains unchanged!)
; return value: #True on success, #False on error
Protected.i t
Rearrange
EndProcedure
;--------------------------------------------------------------------
Procedure.i SortArrayIndexesD (Array a.d(1), Array idx.i(1), mode.i, first.i=0, last.i=-1)
; -- sort the elements of idx(), according to array a()
; in : a() : array with the elements which are to be compared
; idx(): index array which is to be sorted
; mode : #PB_Sort_Ascending / #PB_Sort_Descending
; first: first element to sort
; last : last element to sort
; out: idx(): index array, completely or partly sorted:
; idx(first) is the index of the smallest (biggest) element in (the sorted part of) a(),
; idx(first+1) is the index of the second smallest (biggest) element in (the sorted part of) a(),
; etc.
; return value: #True on success, #False on error
Protected.i i, j, k, flag
Protected.i m, n, r, s, lo, hi
Protected Dim h.i(0)
MergeSort()
EndProcedure
Procedure.i RearrangeArrayD (Array idx.i(1), Array a.d(1), first.i=0, last.i=-1)
; -- rearrange the elements of array a() in place, according to idx()
; in : idx(): idx(first) is the index of the smallest (biggest) element in (the sorted part of) a(),
; idx(first+1) is the index of the second smallest (biggest) element in (the sorted part of) a(),
; etc.
; a() : array that is to be rearranged
; first: first element taken into account
; last : last element taken into account
; out: a(): rearranged array
; (idx() remains unchanged!)
; return value: #True on success, #False on error
Protected.d t
Rearrange
EndProcedure
;--------------------------------------------------------------------
Procedure.i SortArrayIndexesS (Array a.s(1), Array idx.i(1), mode.i, first.i=0, last.i=-1)
; -- sort the elements of idx(), according to array a()
; in : a() : array with the elements which are to be compared
; idx(): index array which is to be sorted
; mode : #PB_Sort_Ascending / #PB_Sort_Descending; #PB_Sort_NoCase
; first: first element to sort
; last : last element to sort
; out: idx(): index array, completely or partly sorted:
; idx(first) is the index of the smallest (biggest) element in (the sorted part of) a(),
; idx(first+1) is the index of the second smallest (biggest) element in (the sorted part of) a(),
; etc.
; return value: #True on success, #False on error
Protected.i i, j, k, flag
Protected.i m, n, r, s, lo, hi
Protected Dim h.i(0)
If mode & #PB_Sort_NoCase
MergeSort(LCase)
Else
MergeSort()
EndIf
EndProcedure
Procedure.i RearrangeArrayS (Array idx.i(1), Array a.s(1), first.i=0, last.i=-1)
; -- rearrange the elements of array a() in place, according to idx()
; in : idx(): idx(first) is the index of the smallest (biggest) element in (the sorted part of) a(),
; idx(first+1) is the index of the second smallest (biggest) element in (the sorted part of) a(),
; etc.
; a() : array that is to be rearranged
; first: first element taken into account
; last : last element taken into account
; out: a(): rearranged array
; (idx() remains unchanged!)
; return value: #True on success, #False on error
Protected.s t
Rearrange
EndProcedure
;-====================================================================
Procedure.i SortParallelArraysII (Array a.i(1), Array b.i(1), mode.i, first.i=0, last.i=-1)
; in : a() : array with the elements which are to be compared and sorted
; b() : array which is to be arranged in the same way as a()
; mode : #PB_Sort_Ascending / #PB_Sort_Descending
; first: first element to sort
; last : last element to sort
; out: a(): completely or partly sorted
; b(): completely or partly sorted in the same way as a()
; return value: #True on success, #False on error
Protected.i r1, r2, r3
Protected Dim idx.i(0)
r1 = SortArrayIndexesI(a(), idx(), mode, first, last)
r2 = RearrangeArrayI(idx(), a(), first, last)
r3 = RearrangeArrayI(idx(), b(), first, last)
ProcedureReturn Bool(r1 And r2 And r3)
EndProcedure
Procedure.i SortParallelArraysID (Array a.i(1), Array b.d(1), mode.i, first.i=0, last.i=-1)
; in : a() : array with the elements which are to be compared and sorted
; b() : array which is to be arranged in the same way as a()
; mode : #PB_Sort_Ascending / #PB_Sort_Descending
; first: first element to sort
; last : last element to sort
; out: a(): completely or partly sorted
; b(): completely or partly sorted in the same way as a()
; return value: #True on success, #False on error
Protected.i r1, r2, r3
Protected Dim idx.i(0)
r1 = SortArrayIndexesI(a(), idx(), mode, first, last)
r2 = RearrangeArrayI(idx(), a(), first, last)
r3 = RearrangeArrayD(idx(), b(), first, last)
ProcedureReturn Bool(r1 And r2 And r3)
EndProcedure
Procedure.i SortParallelArraysIS (Array a.i(1), Array b.s(1), mode.i, first.i=0, last.i=-1)
; in : a() : array with the elements which are to be compared and sorted
; b() : array which is to be arranged in the same way as a()
; mode : #PB_Sort_Ascending / #PB_Sort_Descending
; first: first element to sort
; last : last element to sort
; out: a(): completely or partly sorted
; b(): completely or partly sorted in the same way as a()
; return value: #True on success, #False on error
Protected.i r1, r2, r3
Protected Dim idx.i(0)
r1 = SortArrayIndexesI(a(), idx(), mode, first, last)
r2 = RearrangeArrayI(idx(), a(), first, last)
r3 = RearrangeArrayS(idx(), b(), first, last)
ProcedureReturn Bool(r1 And r2 And r3)
EndProcedure
;--------------------------------------------------------------------
Procedure.i SortParallelArraysDI (Array a.d(1), Array b.i(1), mode.i, first.i=0, last.i=-1)
; in : a() : array with the elements which are to be compared and sorted
; b() : array which is to be arranged in the same way as a()
; mode : #PB_Sort_Ascending / #PB_Sort_Descending
; first: first element to sort
; last : last element to sort
; out: a(): completely or partly sorted
; b(): completely or partly sorted in the same way as a()
; return value: #True on success, #False on error
Protected.i r1, r2, r3
Protected Dim idx.i(0)
r1 = SortArrayIndexesD(a(), idx(), mode, first, last)
r2 = RearrangeArrayD(idx(), a(), first, last)
r3 = RearrangeArrayI(idx(), b(), first, last)
ProcedureReturn Bool(r1 And r2 And r3)
EndProcedure
Procedure.i SortParallelArraysDD (Array a.d(1), Array b.d(1), mode.i, first.i=0, last.i=-1)
; in : a() : array with the elements which are to be compared and sorted
; b() : array which is to be arranged in the same way as a()
; mode : #PB_Sort_Ascending / #PB_Sort_Descending
; first: first element to sort
; last : last element to sort
; out: a(): completely or partly sorted
; b(): completely or partly sorted in the same way as a()
; return value: #True on success, #False on error
Protected.i r1, r2, r3
Protected Dim idx.i(0)
r1 = SortArrayIndexesD(a(), idx(), mode, first, last)
r2 = RearrangeArrayD(idx(), a(), first, last)
r3 = RearrangeArrayD(idx(), b(), first, last)
ProcedureReturn Bool(r1 And r2 And r3)
EndProcedure
Procedure.i SortParallelArraysDS (Array a.d(1), Array b.s(1), mode.i, first.i=0, last.i=-1)
; in : a() : array with the elements which are to be compared and sorted
; b() : array which is to be arranged in the same way as a()
; mode : #PB_Sort_Ascending / #PB_Sort_Descending
; first: first element to sort
; last : last element to sort
; out: a(): completely or partly sorted
; b(): completely or partly sorted in the same way as a()
; return value: #True on success, #False on error
Protected.i r1, r2, r3
Protected Dim idx.i(0)
r1 = SortArrayIndexesD(a(), idx(), mode, first, last)
r2 = RearrangeArrayD(idx(), a(), first, last)
r3 = RearrangeArrayS(idx(), b(), first, last)
ProcedureReturn Bool(r1 And r2 And r3)
EndProcedure
;--------------------------------------------------------------------
Procedure.i SortParallelArraysSI (Array a.s(1), Array b.i(1), mode.i, first.i=0, last.i=-1)
; in : a() : array with the elements which are to be compared and sorted
; b() : array which is to be arranged in the same way as a()
; mode : #PB_Sort_Ascending / #PB_Sort_Descending; #PB_Sort_NoCase
; first: first element to sort
; last : last element to sort
; out: a(): completely or partly sorted
; b(): completely or partly sorted in the same way as a()
; return value: #True on success, #False on error
Protected.i r1, r2, r3
Protected Dim idx.i(0)
r1 = SortArrayIndexesS(a(), idx(), mode, first, last)
r2 = RearrangeArrayS(idx(), a(), first, last)
r3 = RearrangeArrayI(idx(), b(), first, last)
ProcedureReturn Bool(r1 And r2 And r3)
EndProcedure
Procedure.i SortParallelArraysSD (Array a.s(1), Array b.d(1), mode.i, first.i=0, last.i=-1)
; in : a() : array with the elements which are to be compared and sorted
; b() : array which is to be arranged in the same way as a()
; mode : #PB_Sort_Ascending / #PB_Sort_Descending; #PB_Sort_NoCase
; first: first element to sort
; last : last element to sort
; out: a(): completely or partly sorted
; b(): completely or partly sorted in the same way as a()
; return value: #True on success, #False on error
Protected.i r1, r2, r3
Protected Dim idx.i(0)
r1 = SortArrayIndexesS(a(), idx(), mode, first, last)
r2 = RearrangeArrayS(idx(), a(), first, last)
r3 = RearrangeArrayD(idx(), b(), first, last)
ProcedureReturn Bool(r1 And r2 And r3)
EndProcedure
Procedure.i SortParallelArraysSS (Array a.s(1), Array b.s(1), mode.i, first.i=0, last.i=-1)
; in : a() : array with the elements which are to be compared and sorted
; b() : array which is to be arranged in the same way as a()
; mode : #PB_Sort_Ascending / #PB_Sort_Descending; #PB_Sort_NoCase
; first: first element to sort
; last : last element to sort
; out: a(): completely or partly sorted
; b(): completely or partly sorted in the same way as a()
; return value: #True on success, #False on error
Protected.i r1, r2, r3
Protected Dim idx.i(0)
r1 = SortArrayIndexesS(a(), idx(), mode, first, last)
r2 = RearrangeArrayS(idx(), a(), first, last)
r3 = RearrangeArrayS(idx(), b(), first, last)
ProcedureReturn Bool(r1 And r2 And r3)
EndProcedure
;-====================================================================
Macro CalcRanks (_type_)
Protected.i i, k, lo, last=ArraySize(a())
Protected.d v
Protected Dim idx.i(0)
If SortArrayIndexes#_type_(a(), idx(), #PB_Sort_Ascending) = #False
ProcedureReturn #False ; error
EndIf
Dim r(last)
lo = 1
For i = 1 To last
If a(idx(i-1)) < a(idx(i))
v = (lo+i) / 2.0
For k = lo-1 To i-1
r(idx(k)) = v
Next
lo = i + 1
EndIf
Next
v = (lo+i) / 2.0
For k = lo-1 To i-1
r(idx(k)) = v
Next
ProcedureReturn #True ; success
EndMacro
;--------------------------------------------------------------------
Procedure.i RankI (Array a.i(1), Array r.d(1))
; -- works like rank() in the R language, using the default way
; of handling ties (ties.method='average')
; in : a(): integer array, can contain ties (i.e., equal values)
; out: r() : array with the ranks of all elements in a()
; return value: #True on success, #False on error
CalcRanks(I)
EndProcedure
Procedure.i RankD (Array a.d(1), Array r.d(1))
; -- works like rank() in the R language, using the default way
; of handling ties (ties.method='average')
; in : a(): double array, can contain ties (i.e., equal values)
; out: r() : array with the ranks of all elements in a()
; return value: #True on success, #False on error
CalcRanks(D)
EndProcedure
Procedure.i RankS (Array a.s(1), Array r.d(1))
; -- works like rank() in the R language, using the default way
; of handling ties (ties.method='average')
; in : a(): string array, can contain ties (i.e., equal values)
; out: r() : array with the ranks of all elements in a()
; return value: #True on success, #False on error
CalcRanks(S)
EndProcedure
EndModule
CompilerIf #PB_Compiler_IsMainFile
; -- Module demo
EnableExplicit
Define.i n, k, mode
Define out$
Debug "-- SortParallelArraysII()"
n = 5
mode = #PB_Sort_Ascending
; mode = #PB_Sort_Descending
Dim a.i(n-1)
Dim b.i(n-1)
a(0) = 999 : b(0) = 123
a(1) = 5 : b(1) = 1
a(2) = 8 : b(2) = 2
a(3) = 2 : b(3) = 3
a(4) = 111 : b(4) = 456
For k = 0 To n-1
Debug RSet(Str(a(k)), 3) + " " + RSet(Str(b(k)), 3)
Next
; sort both arrays a() and b(), according to the values in a()
If SortAI::SortParallelArraysII(a(), b(), mode, 1, 3) = #False
Debug "Error in procedure SortAI::SortParallelArraysII()"
End
EndIf
Debug ""
For k = 0 To n-1
Debug RSet(Str(a(k)), 3) + " " + RSet(Str(b(k)), 3)
Next
Debug ""
Debug "-- SortParallelArraysDD()"
n = 5
mode = #PB_Sort_Ascending
; mode = #PB_Sort_Descending
Dim ad.d(n-1)
Dim bd.d(n-1)
For k = 0 To n-1
ad(k) = Random(n*1000)/1000
bd(k) = Random(n*1000)/1000
Debug StrD(ad(k),3) + " " + StrD(bd(k),3)
Next
; sort both arrays ad() and bd(), according to the values in ad()
If SortAI::SortParallelArraysDD(ad(), bd(), mode) = #False
Debug "Error in procedure SortAI::SortParallelArraysDD()"
End
EndIf
Debug ""
For k = 0 To n-1
Debug StrD(ad(k),3) + " " + StrD(bd(k),3)
Next
Debug ""
Debug "-- SortParallelArraysSI()"
n = 5
; mode = #PB_Sort_Ascending
mode = #PB_Sort_Ascending | #PB_Sort_NoCase
; mode = #PB_Sort_Descending
; mode = #PB_Sort_Descending | #PB_Sort_NoCase
Dim a$ (n-1)
Dim b.i(n-1)
a$(0) = "Lisa"
a$(1) = "Pete"
a$(2) = "Mary"
a$(3) = "Paul"
a$(4) = "jule"
For k = 0 To n-1
b(k) = k + 1
Debug a$(k) + " " + Str(b(k))
Next
; sort both arrays a$() and b(), according to the values in a$()
If SortAI::SortParallelArraysSI(a$(), b(), mode) = #False
Debug "Error in procedure SortAI::SortParallelArraysSI()"
End
EndIf
Debug ""
For k = 0 To n-1
Debug a$(k) + " " + Str(b(k))
Next
Debug ""
Debug "-- RankI()"
n = 8
Dim a.i(n-1)
Dim R_a.d(0)
; -- Sample data
a(0) = 3
a(1) = 5
a(2) = 2
a(3) = 1
a(4) = 2
a(5) = 2
a(6) = 3
a(7) = 4
If SortAI::RankI(a(), R_a()) = #False
Debug "Error in procedure SortAI::RankI()"
End
EndIf
out$ = "Data: "
For k = 0 To n-1
out$ + " " + LSet(Str(a(k)), 3)
Next
Debug out$
out$ = "Rank: "
For k = 0 To n-1
out$ + " " + LSet(StrD(R_a(k), 1), 3)
Next
Debug out$
Debug ""
Debug "Handling of ties:"
Debug "After sorting, the three data elements with value 2 are at positions 2, 3, and 4."
Debug "Because the 'average' method is used here for ties, all three elements get the rank 3.0."
Debug "The two elements with value 3 are at positions 5 and 6. They both get the rank 5.5."
CompilerEndIf
-------------------------------------------------
My best tricks & tips from 15+ years
Create arrays elegantly
Extended date library
Save JSON data with object members well-arranged
Evaluate and process math expressions
Functions for sets
Statistics with R
Thue-Morse sequence
Natural sorting
Sort array indexes and parallel arrays
Time profiling
VectorIcons