Mailing List Archive: 49091 messages

# Sort, history and present

### [1/1] from: lmecir:volny:cz at: 13-Dec-2010 15:13

A sort-related discussion from the Core group in Rebol3 AltMe world reminded me my post to this list (in discussion with Tim Peters): ***Msort0*** #[[Ladislav-1998 A few remarks to the Quick Sort benchmark. The source was criticized, because of it's lack to handle the special cases. The problem is, that when you improved it, it didn't correspond to the source, so there was no way, how to compare the times. But, for me, it served well. It has proven the capability of Rebol to "mechanically" translate some usual constructs in other languages in no time. That feature may be needed some time. Here is another sorting method - The Merge Sort. You may try to compare the times, it looks good, here is the example: elem time 10 0:00 100 0:00:02 500 0:00:09 1000 0:00:20 REBOL[ Title: "Merge Sort" Author: Ladislav Mecir Email:lmecir-geocities.com Date: 8/2/1998 Purpose: { sort a series } ] compare: func [a b] [ return a< b ] div2: func [x] [ if (x // 2) = 0 [return x / 2] else [return (x - 1) / 2] ] msort: func [a] [ msort_do a length? a ] msort_do: function [a l] [b lb c lc] [ if l<= 1 [return a] else [ lb: div2 l b: (msort_do a lb) lc: l - lb c: (msort_do skip a lb lc) merge b lb c lc ] ] merge: function [a la b lb] [res cont] [ res: head insert/part tail copy/part a la b lb cont: true while [cont] [ if (compare first a first b) [ change res first a a: next a la: la - 1 cont: la> 0 ] else [ change res first b b: next b lb: lb - 1 cont: lb> 0 ] res: next res ] if la> 0 [change/part res a la] else [change/part res b lb] return head res ] Ladislav-1998]] Since the code is 1.x, it uses ELSE. A translation not using ELSE and workable in R3 can look as follows: compare: :lesser-or-equal? div2: func [x][to integer! x / 2] msort0: func [a][msort_do a length? a] msort_do: function [a l][b lb c lc][ either l<= 1 [return a][ lb: div2 l b: (msort_do a lb) lc: l - lb c: (msort_do skip a lb lc) merge b lb c lc ] ] merge: function [a la b lb][res cont][ res: make block! la + lb cont: true while [cont][ either (compare first a first b)[ append/only res first a a: next a la: la - 1 cont: la> 0 ][ append/only res first b b: next b lb: lb - 1 cont: lb> 0 ] res: next res ] either la> 0 [append/part res a la][append/part res b lb] head res ] Results: a: copy [] repeat i 500 [append a i] random/seed 0 random a
>> time-block [msort0 copy a] 0,05
== 0.009 ***Msort1*** Since the memory consumption of the above code was O(N*log N), I almost immediately rewrote it to consume only O(N) memory instead. The code: msort1: function [a compare] [msort-do merge] [ if (length? a)< 2 [return a] ; define a recursive Msort-do function msort-do: function [a b l] [mid] [ either l< 4 [ if l = 3 [msort-do next b next a 2] merge a b 1 next b l - 1 ] [ mid: make integer! l / 2 msort-do b a mid msort-do skip b mid skip a mid l - mid merge a b mid skip b mid l - mid ] ] ; function Merge is the key part of the algorithm merge: func [a b lb c lc] [ until [ either (compare first b first c) [ change/only a first b b: next b a: next a zero? lb: lb - 1 ] [ change/only a first c c: next c a: next a zero? lc: lc - 1 ] ] loop lb [ change/only a first b b: next b a: next a ] loop lc [ change/only a first c c: next c a: next a ] ] msort-do a copy a length? a a ] Since the code consumed less memory, it also ran faster:
>> time-block [msort1 copy a :lesser-or-equal?] 0,05
== 0.00825 ***Msort(2)*** While the above code is using less memory, there is a way how to make the merge even "more in place", which leads to the code: msort: function [ {merge-sort a series in place} a [series!] compare [any-function!] ][msort-do merge][ ; define a recursive Msort-do function msort-do: function [a l][mid b][ either l<= 2 [ unless any [ l< 2 compare first a second a ][ set/any 'b first a change/only a second a change/only next a get/any 'b ] ][ mid: to integer! l / 2 msort-do a mid msort-do skip a mid l - mid merge a mid skip a mid l - mid ] ] ; the Merge function is the key part of the algorithm merge: func [a la b lb /local c][ c: copy/part a la until [ either (compare first b first c)[ change/only a first b b: next b a: next a zero? lb: lb - 1 ][ change/only a first c c: next c a: next a zero? length? c ] ] unless zero? length? c [change a c] ] msort-do a length? a a ] I guess, that it will be no surprise for you, that since the code does less shuffling again, it runs even faster:
>> time-block [msort copy a :lesser-or-equal?] 0,05