This was written December 1989 and published in Archive, Issue 3 No.5, and is still used in several of my progams.

Having recently got to grips with ARM code, as an exercise, a friend asked me to write a sort routine for him. This is the resul. It may not the best example of coding but it does the job.

The program was converted from BASIC to ARM code and I have tried to show how easily conversions can be done, by putting the BASIC as REM statements into the assembler.

It is very fast, typically sorting 8000 strings in around 10 to 11 seconds, compared with 9 seconds, using RISCOS's Heap sort, which only moves the pointers. It can also sort from anywhere in the string, is case insensitive (if you want), and just uses 232 bytes once assembled.

There are however some conditions:

1. The strings must be Indirection Strings i.e. $A and not A$(),
2. They must also all be the same length of X*4 including the carriage return, where X is any integer up to 64. This is because the SWAPping of strings is done a word (4 bytes) at a time (lines 920 & 930). It is possible to use single byte in the swap, but it is much slower.

There are advantages to using indirection strings,

1. It can use less memory; BASIC uses 5 bytes for storing addresses. Indirection uses none.
2. The strings are *SAVEd, faster than PRINT#ing them.

The sort works from the second string (string 1) to the end of strings but does not include the first string (string zero). This is due to the original ShellMetzner routine. If used in a database, string zero can be used to store information; field­names, date of saving etc.

The program was based on the following BASIC Shell­Metzner routine which can be downloaded as a Zip with the other files..

1300 REM Shell­Metzner sort algorithm in BASIC
1310 REM Numstr is number ofstrings to be sorted 1320 M$=Numstr
1330 M$=INT(M$/2):IFM$=0 THEN GOTO 1400
1340 J%=1 :K%=Numstr­M%
1350 I$=J%
1360 L$=I%+M%
1370 IF A$(I$)>A$(L$) THEN SWAP A$(I$), A$(L%): I$=I$-M$ :IF I$>0 THEN GOTO 1360
1380 Jg=J%+1 :IF J%>K$ THEN GOTO 1330 ELSE GOTO 1350
1390 REM GOTO 1350
1400 ENDPROC

The GOTO's are the original program and have nothing to do with me! The only alterations I made were to use integer variables, use SWAP in line 1300 and use ELSE GOTO 1350 in line 1380. The latter was done in order to make how the conversion was done, easier to understand.

This BASIC program is surprisingly fast, sorting 1000 strings in 14 seconds. How it works I leave up to you to work out, all I did was convert it to ARM code.

Converting to ARM code

The first thing was to pass the address, string length, number of strings and start point of the sort from BASIC.

RO or A% is the address of the start of string storage RI or B% is the length of the string

R2 or C% is the number of strings (Numstr)

R3 or D% is the start point of the sort (1 st char = 0) In converting the BASIC routine, the first thing I did was designate all the variable names to registers:

M% = R4, J% = R5, K% = R6, I% = R7, L% = R8 It was then just a matter of writing the appropriate code, with jumps to compare and swap subroutines. As BASIC passed four registers which are used throughout the program and there were several registers needed during the program, it was necessary to use registers RO to R10. It would be possible to stick to eight registers but this slows up the sort considerably.

Before going to the assembler version of line 1380, registers 4, 5 and 6 are pushed on the stack.

1380 J$=J%+1: IF J$>K% THEN GOTO 1330 ELSE GOTO 1350

This caused a minor problem in pulling off the stack since, in the BASIC program, there are two GOTO's, hence two stack pulls and two return points.

In both compare and swap routines, it was necessary to find the address of the strings. In BASIC this would be found by

Address = StartAddr + (StringNumber * StringLength)

In ARM code this was no different:

R5 = RO + R7 * Rl and
R6 = RO + R8 * R1

where R7 and R8 are the string numbers, R2 is the length and RO is the start address of storage.

I found that it was quicker to re­calculate the addresses rather than storing and retrieving them.

The compare is done a character at a time, checking for a Carriage Return (ASCII 13) before ANDing with 95 (&9F) which strips the case off the character. (This ANDing can be removed if you wish to use a case sensitive sort.)

The bytes are then compared and if the first is greater than the second, a string swap is done. If they are equal, the next character is obtained and so on until we reach the carriage return. At this point the strings must be identical, so we continue with the next strings.

As long as alpha­numeric characters are used there is no problem with ANDing with 95, but some other characters when ANDed give the same result and can cause problems. If you wish to use ASCII 33 to 255 then you may have to lose case sensitivity by omitting lines 830 and 840.

The swap is done four bytes (a word) at a time and a pointer increased by 4 bytes, until the pointer is the same length as the string, whereupon we return to the sort routine.

330DEFPROCass
340FOR pass = 0 TO 3 STEP 3
350P%=code
360[ OPT pass
370STMFD R13!,{R0-R12,R14} ;store registers
380CMP R3,#0 ;check R3 is positive
390MOVLT R3,#0 ;if not make zero
400SUB R1,R1,#1 ;remove CR from string length
410CMP R3,R1 ;check R3 is less than string length
420MOVGE R3,#0 ;if not make zero
430ADD R1,R1,#1 ;add CR
440
450MOV R4,R2 ;A%=numstr
460
470.loop1 ;LINE 1330
480MOV R4,R4,LSR#1 ;A%=INT(A%/2)
490CMP R4,#0 ;IF A%=0 THEN
500BEQ finish ; GOTO 1400
510MOV R5,#1 ;J%=1
520SUB R6,R2,R4 ;K%=numstr-A%
530
540.loop2 ;LINE 1350
550MOV R7,R5 ;I%=J%
560
570.ret1 ;LINE 1360
580ADD R8,R7,R4 ;
590STMFD R13!,{R4-R6} ;save R4,R5 & R6
600B compare ;do comparison and swap if necessary
610.cont ;LINE 1380
620LDMFD R13!,{R4-R6} ;restore R4,R5 & R6
630.cont2 ;LINE 1380
640ADD R5,R5,#1 ;J%=J%+1
650CMP R5,R6 ;IF J%>K%
660BGT loop1 ; THEN GOTO 1330
670B loop2 ;GOTO1350
680.finish ;LINE 1400
690LDMFD R13!,{R0-R12,R15} ;restore registers and return to BASIC
700
710.compare
720MUL R5,R7,R1 ;calculate address of first string
730ADD R5,R5,R0 ;
740ADD R5,R5,R3 ;get address for start of sort string 1
750MUL R6,R8,R1 ;calculate address of second string
760ADD R6,R6,R0 ;
770ADD R6,R6,R3 ;Get address for start of sort string 2
780.nextchar
790LDRB R9,[R5],#1 ;get byte of string 1
800LDRB R10,[R6],#1 ;get byte of string 2
810CMP R9,#13 ;is it a CR ie end of string
820BEQ cont ; if so continue with sort algorithm
830AND R9,R9,#95 ;ignore case by ANDing byte with 95
840AND R10,R10,#95 ;if case wanted delete lines 830 and 840
850CMP R9,R10 ;compare bytes IF A$(I%) > A$(L%)
855 ; GOTO 1390 (next line)
860BGT swap ;if greater than, THEN SWAP strings
870BEQ nextchar ;if equal then get next character
880B cont ;if less than, then continue with sort
890.swap
900MUL R5,R7,R1 ;Recalculate addresses of strings
910ADD R5,R5,R0 ; (quicker than storing)
920MUL R6,R8,R1
930ADD R6,R6,R0
940MOV R4,#0 ;set pointer to beginning of strings
950.nextswap
960LDR R9,[R5,R4] ;get WORD (4 bytes) of string 1
970LDR R10,[R6,R4] ;get WORD of string 2
980STR R9,[R6,R4] ;swap WORDs in string
990STR R10,[R5,R4]
1000ADD R4,R4,#4 ;increment pointer by WORD length
1010CMP R4,R1 ;compare pointer with string length
1020BLT nextswap ;if not end of string, get next WORD
1030LDMFD R13!,{R4-R6} ;else restore registers
1040SUB R7,R7,R4 ;I%=I%-A%
1050CMP R7,#0 ;IF I%>0 THEN
1060BGT ret1 ; GOTO 1360
1070B cont2 ; ELSE GOTO 1350
1080]
1090NEXT
1100OSCLI("SAVE ARMsort "+STR$~code+" "+STR$~P%)
1110ENDPROC
1120
1130REM 1140
1150DEFPROCsetupstrings
1160a$="The green col. is before the sort and the yellow is from the sort"
1170C%=100:B%=72
1180CLS:PRINT''"Setting up ";C%;" strings of length ";B%;" thus..."'
1190PRINTCHR$(RND(74)+48)+CHR$(RND(74)+48)+CHR$(RND(74)+48)+a$+CHR$(RND(74)+48)+CHR$(RND(74)+48)+CHR$(RND(74)+48)
1200FORN=1TOC%
1210A$=CHR$(RND(74)+48)+CHR$(RND(74)+48)+CHR$(RND(74)+48)+a$+CHR$(RND(74)+48)+CHR$(RND(74)+48)+CHR$(RND(74)+48)
1220 $(string+(N*B%))=A$
1230NEXT
1240ENDPROC
1250
1260REM-----------------------------------------------------------
1270
1280DEFPROCShellMetzner
1290
1300REM Shell-Metzner sort algorithm in BASIC
1310REM Numstr is number of strings to be sorted
1320M%=Numstr
1330M%=INT(M%/2) :IFM%=0 THEN GOTO 1400
1340J%=1 : K%=Numstr-M%
1350I%=J%
1360L%=I%+M%
1370IF A$(I%) > A$(L%) THEN SWAP A$(I%),A$(L%) : I%=I%-M% : IFI% > 0 THEN GOTO 1360
1380J% = J% + 1 : IF J% > K% THEN GOTO 1330
1390GOTO 1350
1400ENDPROC

The most extreme test is to sort from character Included in the BASIC program is a demonstration number three, T, as the sort has to check all the of a sort which sets up C% strings of length 72 characters from the T of The to the t of sort before characters (line 1160) in the formatany differences are seen.

Now use it in your own programs

For those of you who want to use the sort in your own programs, the first thing you have to do is convert your multi­dimensioned arrays to single ones, with two arrays holding the length of each string and the start points.(DIM$)

There are some procedures in the program "Convert" which will attempt to convert your array (if it is possible to convert).

To use this procedure, first write the relevant lines of BASIC to load your array and then fill in what a and b are, i.e. the D IMensions of your strings as A$(a,b). The procedure will tell you the maximum length of each string and the spacing and gives you option of changing the length for a maximum string of 256 (including carriage return).

The only problem you may have in running the program is lack of memory if you have a large array of strings.

You can then decide the lengths you want and store them as DATA statements in your program or read them from the beginning of the store memory, where the length is stored and then the "DIM$" array. Any queries, write to me at the address in the REM statements. (Please send a SAE for replies.) It is also possible, by stating that the start address of strings is at the start of string 45 and that there are only 11 strings to sort part of the array.

I hope you find this routine useful.

Sort routines in RISC­OS

This is a sort using the Heapsort provided within RISC­OS. In order to use, it the strings must be stored as indirection strings and the location of each string must also be stored as a block of 4 byte words (at least until I discover how to find where BASIC stores its strings).

The sort is called using SYS"OS_HeapSort", RO , RI, R2

RO is the number of strings

RI is a pointer to an array of word sized objects, which in this case is a list of 4 byte words holding the address of the strings.

R2 is the type of object to be sorted, 4 is case insensitive strings and 5 is case sensitive strings

For details of other types, see PRM page 819 (vo1.2)

0 REM>Heapsort4A
10 REM Uses SYS"OS HeapSort" (SYS&4A) to sort strings
20
30 DIM A 80100,B 5005
40 N$="This is a string of length approximately78 characters, the79th to be added­ "
50 FORI=OTO1000 : REM make 1000
stringsof length 79
60A$=CHR$(65+RND(57))+N$
70$(A+I*80)=A$:!(B+I*4)=(A+I*80)
80 NEXT
90
100 FORI=OTO1O:PRINT$!(B+I*4):NEXT
110 PRINT­Sorting "'
120
130 TIME=0
140 SYS"OS HeapSort",1000, B,4 :REM
1000 strings,B = Address Store 150 REM 4 Case insensitive 5 Case sensitive 160 170 PRINT"Time ";TIME/100;" s to sort 1000 strings" 180 PRINT 190 FORI=OTO1O:PRINT$!(B+I*4):NEXT 200 END

To download the programs 6KB Zip click here>.