LnSOS BOOT 1.1 SOS.KERNEL SOS KRNLI/O ERRORFILE 'SOS.KERNEL' NOT FOUND%INVALID KERNEL FILE: xةw,@  ȱlmi8#)!)ARTICLE15v; ' '*ARTICLE.156OhB.SORT.2D `V'+BINARY.SORT QU: BUBBLE.SORT ; /BUBL.PNTR.SORT @;  CREATEJUNKFILE UjK Ÿ/ DISKNAME.DAT#l1k5III.BSB.05IC.03u' ARTICLE12ARTICLE13a<2ARTICLE14|<3ARTICLE15; <&*MENU.MAKER K ;%SEG.T >dLԡm#i㰼m#iЕOLԡȱfg hi !dLԡ憦  Ljmkm l y`2 Lԡ8(Je稽)ʈ@LC+FAST.B.SORT \V ,FINAL.B.SORT WWV+INVERT.SORT +PNTR.BUBBLE KT)QUICKSORT GSHELL.SORT U; %SHELL NJSORT.FRAME I n=1255 x=sortval$,zero$) x=03050 value$(n)=sortval$,x-1) sortval$=sortval$,x+1) n value$(n)=sortval$ last=1 i=1n j=last2555 startval%(j)<>0spointer%(i)=startval%(j):3100 j" "ERROR, startval not founx=sortval$,item$)0xpntr%(endval%(x))=rec%:endval%(x)=rec%:x=sortval$)+29x+item$)>255"sortval$ overflow, sort aborted":!sortval$=sortval$+zero$+item$startval%(x)=rec%endval%(x)=rec%  sortval$=sortval$,2) ec% 3000S"Number of unique values: ";n:"Ratio of total records to unique values: ";a/n,"Press return for the sorted list: ";a$'i=1a:array$(sarray%(i));" ";:i$::"Sort the array again? ";a$%a$=a$,1,1):a$="Y"a$="y"100 1)*26)+65)Zj,idi=1a:array$(i);" ";:i1::"Start and end columns for sort: ";c1,c2Li=1a:pntr%(i)=zero%::i=1255:startval%(i)=zero%:endval%(i)=zero%:sortval$=""rec%=1a$item$=array$(rec%),c1,c2-c1+1) 2000 rI pntr%(1000),startval%(255),endval%(255),spointer%(255),sarray%(1000)zero$=0):zero%=0array$(1000),value$(128)(("Number of strings to generate: ";a*2"Number of characters per string: ";b <i=1a Fj=1b(Parray$(i)=array$(i)+rray(upper):temp=temp-span:temp>010608iBspan=span/2)Lspan1030ۿS( sort using Shell-Metzner technique n is number of elements( sarray is the array to be sorted2 n<2span=n/2)newspan=n-spani=1newspan temp=i$upper=temp+spanY.sarray(temp)>sarray(upper)sarray(temp),sa! sort using bubble technique n is number of elements( sarray is the array to be sorted2 dtop=n-1nhaveswapped=0 xi=1top@sarray(i)>sarray(i+1)sarray(i),sarray(i+1):haveswapped=1i haveswappedtop=top-1:110ray(i+1):madeswap=1imadeswaptop=top-1:1010$>! sort using bubble technique N is number of elements( sarray is the array to be sorted2( parray is the pointers to the sorted array2 top=n-1madeswap=0 i=1topMsarray(parray(i))>sarray(parray(i+1))parray(i),pard":  last=j+1& i 0 4000: k=0 D i=1nN index=spointer%(i)EX k=k+1:sarray%(k)=index:pntr%(index)<>0index=pntr%(index):3160v i n<2span=n/2)newspan=n-spani=1newspan temp=iupper=temp+span{value$(temp)>value$(upper)value$(temp),value$(upper):spointer%(temp),spointer%(upper):temp=temp-span:temp>04050ispan=span/2)span4020::;a routine will be contained in a subroutine. The main program looks like this: 100 DIM sarray(1000),stack(100) 110 INPUT"Sort Routine. Number of elements to generate: ";a$ 120 n=CONV(a$):IF n<2 THEN 200 130 FOR i=1 TO n:sarray(i)=RND(1):NEXT Let's look quickly at the Quicksort (heh,heh), and then explore the Binary sort in some detail. The Big Shuffle For simplicity, we'll start with the routine used last time to generate random numbers to sort in an array called "sarray". The actual sortt is also very useful on numeric data where the same principle (lots of items, only a few unique values) applies. This month we will look at a faster version of the Shell Sort, called the Quicksort, and a new sort technique called the "Binary Tree Sort". a somewhat esoteric sort that was referred to as an "inverted list" sort, good for situations where there were a large number of items but only a few values. Although the inverted sort is generally used for string data like state codes, sex codes, etc., ime limited task switching are now possible for clever programmers. Rack up another first for Apple /// and SOS! Sifting through the Sorts Last month we started exploring sort techniques, covering the simple but slow "bubble" sort, the Shell sort, and ly something. One more note: The version of the console driver on the Catalyst disk implements several new events and allows the normal "read with wait" request to the console to be interuptable. This means things like generalized device spooling and sos as easy as hitting a special keystroke and picking the application off the menu. Yes, even protected programs like Visicalc are provided for! If you haven't seen this program yet, zip down to your dealer for a demo or get in touch with Quark, it's realmeans that you can load all your application programs and language interpreters onto mass storage devices (like Profile!) and once the Catalyst program is booted, going from Applewriter to Basic to Visicalc to Senior Analyst to Pascal to System Utilities iy of note as well, a new piece of software from Quark Engineering (the Word Juggler people). The package is called "Catalyst", a perfect name, since it can control the startup and execution of all your Apple /// programs from a single command menu. This  !"#$%&'()*+,-./0123456789:;<=>?@ABCDE Fifteen Lots of exciting things have happened on the Apple /// frontier since last we talked, and if any of you haven't heard about the new products Apple announced at Comdex, you should really check them out at your dealer's. One other product is worth T H E T H I R D B A S I C by Taylor Pohlman Exploring Business Basic, Part 135 PRINT"Start of Sort" 140 GOSUB 1000 150 PRINT"Sort complete. First 10 elements are:" 160 FOR i=1 TO 10:PRINT sarray(i);" ";:NEXT 170 PRINT:PRINT:GOTO 110 200 END As you can see, line 130 generates a set of random values in "sarray" and then a GOSUB is performed to sort the array. As we discussed last time, one way to improve the performance of "exchange" type sorts (the name which the bubble, shell and quick sorts share) is to make the exchanges cover as much territory as possible i PRINT TIME$;" End of sort" 150 INPUT"Sort complete. Do you want to list the sorted records?";a$ 160 IF a$<>"Y" AND a$<>"y" THEN 110 170 FOR i=1 TO sarray(0):INPUT#1,sarray(i);a$:PRINT a$:NEXT 180 GOTO 110 200 END The main routine has bee. Filename to sort: ";a$ 120 IF a$="" THEN GOTO 200:ELSE:OPEN#1 AS INPUT,a$ 125 ON EOF#1 LET sarray(0)=i-1:n=sarray(0):GOTO 140 130 PRINT TIME$;" Start of sort" 135 FOR i=1 TO 1000:sarray(i)=i:INPUT#1,i;sort$(i):NEXT 140 GOSUB 1000 145 ray. The example below takes string data from a file and performs a pointer sort. Remember that is is generally more efficient to swap numeric pointers than to swap strings. Voila: 100 DIM sarray(1000),stack(100),sort$(1000) 110 INPUT"Sort Routin random order. One last example of quicksort would be appropriate. The example above used the direct sorting of numeric data as an example. Quicksort can be used just as easily to sort string data, or simply create sorted pointers to any type of data arfaster than the shell sort, except in those circumstances where the data is already sorted, or nearly so. Shell sorts will work through such arrays faster than the quicksort, which takes almost as long to arrange sorted data as to arrange data which is inn about each partition of the array, and to work its way through each partitioned set until all are sorted. Note also the extensive use of the SWAP command, to exchange values in the most efficient manner possible. The quicksort algorithm is generally attribute that all variables are global, in other words, each occurance of a subroutine will use the same variables over again, forgetting their previous state. For those reasons, and others, the routine above uses the "stack" array to maintain informatioprogramming languages, this would be handled with a technique called "recursion". Recursion simply means that a routine can call itself, without limit. Basic not only has limits on how many times a GOSUB can be executed without a RETURN, it also has the nd points and check for completion 1130 m=stack(l)+1:l=l-1:IF l>0 THEN 1010 1140 RETURN Several things are worthy of note here. First, it is necessary to use the same code to operate on each of the partitions of data which will be sorted. In some 1 1070 IF j<>mid THEN SWAP sarray(i),sarray(mid) 1080 l=l+1:stack(l)=i:GOTO 1010 1090 REM check for cases of 1 or 2 elements 1100 IF j-m<2 THEN 1130 1110 IF sarray(m)>=sarray(m+1) THEN SWAP sarray(m),sarray(m+1) 1120 REM Set begin and ej-m<3 THEN 1100 1030 mid=INT((i+j)/2) 1040 i=i+1:IF i=j THEN 1060:ELSE:IF sarray(i)<=sarray(mid) THEN 1040 1050 j=j-1:IF i<>j THEN IF sarray(j)=mid THEN i=i-ated group of instructions, far less iterations of the code have to be performed, and thus faster performance is possible. Here's the routine: 1000 REM Initialize begin and end points 1005 l=1:stack(1)=n+1:m=1 1010 j=stack(l):i=m-1 1020 IF ed on the opposite ends of the array. The quicksort gets further speed by successively partitioning the sets of values into smaller groups and working on each until it is small enough to be sorted by simple swaps. While this makes for a much more complicn one swap. Quicksort algorithms go this one better by first picking a value which is a guess as the the midpoint of the values in the set, and then swapping other values based on this hypothetical middle value. Furthermore, the initial swaps are performen changed considerably. "Sort$" has been added as a string array holding the values to be sorted. The string values are to be read from a random access text file instead of being generated by the program. In addition, line 125 sets up the ON EOF statement which will detect the end of data, set the appropriate values, and start the sort. Note also that the TIME$ function is being used. If you don't have a clock chip, you can time this yourself. One of the advantages of using a file for input is that yoeration is complete. This is also a favorite trick of people using Visicalc during "recalculates" or "loads". The console driver automatically turns the screen back on when the next input request occurs. Living in a Tree Although the Shell and Quicksole /// will still write information to the screen, but having it turned off speeds up operations by as much as 30 percent. This can make quite a difference when sorting or calculating, especially when you usually don't need to see the results until the op through the Quicksort routine above should finish in about 40 seconds. NOTE: Don't forget about turning the screen off during sorting! This can be done by a CTRL-5 from the keyboard, or by programatically printing an ASCII 14 to the console. The Appow. Generating 200 records should be sufficient for testing the sort routines. Any more would take too long without proving anything, and less would make it difficult to measure the consequences of changes to the program. For example, running 200 records DEACF 2319 ABDDC 4982 FFBBA 1965 A slightly fancier version would prompt for the name of the file to be created, the record lenght, etc., but this will serve nicely for the examples to follfollowing properties: 10 characters long, the first five characters consisting of random occurrences of the letters "A" through "F", then a space character followed by a random four digit number. Examples of the records produced look like this: ND(1))) 35 NEXT J 38 A$=A$+" " 41 FOR K=1 TO 4 42 A$=A$+CHR$(48+INT(10*RND(1))) 43 NEXT K 45 PRINT A$ 47 PRINT#1,I;A$ 50 NEXT I 60 CLOSE 70 END This routine is designed to create random strings with the ul to test the sort routines, and will come in handy later on in this article. It looks like this: 5 OPEN#1,"JUNKFILE",12 6 INPUT"NUMBER OF RECORDS TO CREATE: ";N 10 FOR I=1 TO N 15 A$="" 20 FOR J=1 TO 5 30 A$=A$+CHR$(65+INT(6*Rs in "sarray" are used in line 170 to look up the records in sorted order. To effectively use this program as it stands, you need a program which will generate text files to be sorted. The program below will create a file of random "junk" which is usefr completion 1130 m=stack(l)+1:l=l-1:IF l>0 THEN 1010 1140 RETURN Note the changes is 1040, 1050 and 1110 to have the IF statements test the proper element of "sort$" and swap the pointers if necessary. After the sort is complete, the pointer value(i),sarray(mid) 1080 l=l+1:stack(l)=i:GOTO 1010 1090 REM check for cases of 1 or 2 elements 1100 IF j-m<2 THEN 1130 1110 IF sort$(sarray(m))>=sort$(sarray(m+1)) THEN SWAP sarray(m),sarray(m+1) 1120 REM Set begin and end points and check foIF i=j THEN 1060:ELSE:IF sort$(sarray(i))<=sort$(sarray(mid)) THEN 1040 1050 j=j-1:IF i<>j THEN IF sort$(sarray(j))=mid THEN i=i-1 1070 IF j<>mid THEN SWAP sarrayse "sarray" as a pointer array, so that the strings would not have to be directly swapped: 1000 REM Initialize begin and end points 1005 l=1:stack(1)=n+1:m=1 1010 j=stack(l):i=m-1 1020 IF j-m<3 THEN 1100 1030 mid=INT((i+j)/2) 1040 i=i+1:u can make multiple runs under the same conditions to test the efficiency of particular sorting algorithms. Line 135 uses "sarray" as a pointer array to the actual position of the "sort$" values. The subroutine at line 1000 was consequently modified to urt algorithms are quite efficient, they share one disadvantage which makes them difficult to use in some circumstances. There are occasions in processing data when "multi-level" sorts are desired. That is, somebody wants an address list arranged alphabetically by zip code. This means that the printout will group all people with the same zip code together, and list them alphabetically within each zip code group. To accomplish this multi-level sort, you must first sort alphabetically, and then use that orovely, and can certainly be accomplished with very few comparisons, what does it have to do with sorting? Good question! In a sense, it shares something of the technique used in quicksort, since we have partitioned the data into very small groups which ha * Fred June Sue * * * * * * * * Frank George Martha William While all this seems lis: Jim * * * * Bill Nancy * * * * * laced on a right-hand branch. "June", however, while being greater than "Jim", is less than "Nancy", and therefore goes to the left-hand branch. Constructing the rest of the tree with "George", "William", "Martha" and "Frank" gives a final result like ththat entry ("Bill") was examined. Since "Fred" is greater than "Bill", "Fred" was attached to a right-hand branch below "Bill". In the same way, "Sue" is greater than "Jim", so "Nancy" is checked, and since "Sue" is still greater than "Nancy", "Sue" is p * * * * * Fred June Sue Reviewing the process quickly, it went something like this: "Fred" was less than "Jim", but there is already an entry on the right branch, so lets add "Fred", "Sue" and "June". The tree now looks like this: Jim * * * * Bill Nancy * * Bill Since the name "Bill" evaluates as "less than" the name "Jim", it is placed on a branch to the left. The next name, "Nancy" goes on a right-hand branch, since it is greater than "Jim". Following this logic,d branches off the tree, we take each element of the list, one at a time, and decide whether it should be connected on the right or left side of the root. For example: Jim * Martha Frank To arrange these names into a binary tree structure, we take the first name on the list as our starting point, or root. For the sake of simplicity, we'll consider this to be an upside-down tree, with the root at the top. To builes behind a binary tree data structure is a worthwhile exercise. First, let's consider a list of names which we'd like to arrange in order: Jim Bill Nancy Fred Sue June George William inary tree" sort. This sort has the added virtue of very rapid insertion of additional records, once the existing records are sorted, making it also very suitable as an access method. Before getting into the routine itself, an examination of the principll order of the data, making multi-level sorts impossible to implement. Our old friend, the bubble sort, does preserve order, but is impossibly slow. There are several sort techniques which solve this problem, but the one chosen for this article is the "bder to sort everybody by zip code. This implies that each sort must perserve the physical order of the previous sort. Unfortunately, the very fact which speeds up Shell sorts and quick sorts, the swapping of data over long distances, destroys the originave a relationship to each other. It is these interrelationships which permit building a sorted list from this structure very quickly. Two things should be obvious. No matter what value is used as a starting point, clearly the value on the extreme left-hand side of the tree is the smallest, and the value on the extreme right-hand side is the largest. To assemble the list in ascending order, then, you must first go to the left until there are no more branches leading left. In this tree, that value is "Biabove. "Parray%" will hold the sorted list of pointers, and "slist%" holds the initial list of pointers as read from the file. "Sort$", as you might expect, holds the sort keys to be examined, and "stack%" holds temporary pointers used in assembling the ,sortpointl%(1000),sortpointr%(1000) 20 DIM slist%(1000),sort$(1000),stack%(200) 25 z=0:o1=1:o2=2 These lines do the initialization. Note especially the "sortpointl%" and "sortpointr%" arrays. These will hold the left and right pointers described from B-tree University The long-winded explanation above was designed to make you so ready to examine this month's program that your fingers itched to type it in. Wait no longer: 5 REM File sort based on Binary Tree algorithm 10 DIM parray%(1000)une 0 9 7 George 0 0 8 William 0 0 9 Martha 0 0 10 Frank 0 0 Graduation Jim 2 3 2 Bill 0 4 3 Nancy 6 5 4 Fred 10 7 5 Sue 0 8 6 Jmation, simply by indicating for each item what items are immediately below it in the tree. Using zero to indicate that the particular branch is empty, the new list would look like this: item number item value left pointer right pointer 1al list of names, with their associated pointers: 1 = Jim 2 = Bill 3 = Nancy 4 = Fred 5 = Sue 6 = June 7 = George 8 = William 9 = Martha 10 = Frank We can assemble another list next to this one, which contains all the binary tree infor position in the tree structure itself is enough to establish the order. Furthermore, if we store pointers in the tree, instead of the actual items themselves, it is not necessary to move the items at all. To show how this would work, consider the origin Further, if the tree is relatively well balanced (more on that later), it doesn't take many comparisons to establish a place for the item. Once the "tree" is build, a sorted list can be obtained easily, and without re-examining the item values, since thel lot of trouble to go to in order to sort a list of ten names, you should notice a few things which make this technique powerful. First, we are ordering the data as it is initially examined, and once we find a place for an item, it is never moved again. liam". All that now allows us to say that the sorted order is: Bill Frank Fred George Jim June Martha Nancy Sue William Although this seems like an awfubranches, "June" is added as the next list item, and then "Martha". Since that branch is exhausted, "Nancy" is next, and then exploring "Nancy"'s other branch finds no left hand branches to add. That finishes the list, with the addition of "Sue" and "Wilred") and "George" is next, since it is the last item left on the left-hand side of the tree. That takes up to the top of the tree, where "Jim" gets added to the list, and the right-hand side is explored for its smallest (left-most) value. Following the ll". That becomes the first item in the sorted list. Next, we go down "Bill"'s right branch, to its left-most value. That's "Frank", the next item in the list. "Fred" is next, because it must be greater than "Frank" (since "Frank" was to the left of "Fb-tree structure into a sorted item list. Next comes the user input and setup section: 30 HOME:PRINT"Prepare a sorted list" 40 PRINT:INPUT"Name of file to sort: ";a$ 45 IF a$="" THEN 400 50 IF LEN(a$)>11 THEN PRINT"Filenames must have a maximum of 11 characters":GOTO 40 55 OPEN#1 AS INPUT,a$ 60 PRINT:INPUT"Choose the beginning and ending columns to sort on: ";b,e 65 IF b<1 OR e=sort$(testrec) THEN 570 550 IF sortpointl%(testrec) THEN testrec=sortpointl%(testrec):GOTO 540 560 sortpointlords. Note that although the user may have only chosen to sort on a small portion of the record, the routine at line 380 reads and prints the entire record. Next, let's examine the Binary tree build routine: 490 REM Routine to build the B-tree 500 cture, and the subroutine at line 800 which builds the sorted pointers in the "parray%" array by decoding the structure in the b-tree. After that, the list of sorted pointers is stored in the key file, and the user is optionally allowed to list out the rec(0):INPUT#o1,parray%(i);a$:PRINT a$:NEXT:GOTO 40 400 PRINT:PRINT"End of sort program." 410 CLOSE:END As you can see, the main operations of the program are handled in the subroutine at line 500 which reads records and constructs the binary tree strulist 340 PRINT:PRINT"Storing sorted list" 350 WRITE#2,0;parray%(0) 360 FOR i=1 TO parray%(0):WRITE#o2;parray%(i):NEXT 370 PRINT"Sorted list stored." 375 INPUT"Print sorted records? ";a$:IF a$<>"y" AND a$<>"Y" THEN 40 380 FOR i=1 TO slist%sed is serial from the main file, or from the previously sorted list. Next, let's look at the rest of the main routine: 130 PRINT TIME$ 150 GOSUB 500:REM build the B-tree 270 PRINT:PRINT 275 PRINT TIME$ 300 GOSUB 800:REM create the sorted orting on a subset of the whole record, permits multi-level sorting to be done. Line 75 opens this file, and line 80 checks to see if there is valid data there. Line 95 then initializes the pointer array "slist%" depending on whether the sequence to be u2;slist%(i):NEXT Notice that the section above has a couple of interesting features. The limitation of 11 characters in the filename allows the creation of a "key" file which stores the current sorted list. This, together with the feature which allows s#2,0:IF TYP(2)<>2 THEN 90 85 INPUT"Do you wish to sort using the existing sorted order? ";a$ 90 a$=MID$(a$,1,1) 95 IF a$<>"y" AND a$<>"Y" THEN slist%(0)=1000:FOR i=1 TO 1000:slist%(i)=i: NEXT:ELSE:READ#2;slist%(0):FOR i=1 TO slist%(0):READ#oopriate place for the value. Note the double use of the NEXT statement. Executing either one will take the next value in the loop, and saves executing a GOTO. Because it is impossible to know which next will be executed last, both need to be followed by RETURN statements. It is a good idea to study the action of this routine with the b-tree data example given previously, to be sure you follow what is happening. After the b-tree structure is built, the routine at line 800 decodes it, and builds the sorFH% Initialize begin and end pointsl=1:stack(1)=n+1:m=1j=stack(l):i=m-1j-m<31100mid=(i+j)/2)4i=i+1:i=j1060::sarray(i)<=sarray(mid)1040Kj=j-1:i<>jsarray(j)=midi= dsarray(1000),stack(100)9n"Sort Routine. Number of elements to generate: ";a$xn=a$):n<2200i=1n:sarray(i)=1):"Start of Sort" 1000,"Sort complete. First 10 elements are:"i=110:sarray(i);" ";: ::110 and right until the records are all read. Anyway, try some things out and see what you think. The "junkfile" program will allow all the practice using this routine that you can stand. Until next month, then, tell your friends that you can't leave the hlve this problem, but they were left out this time for simplicity. One technique completely outside the usual approaches is to modify the routine in line 700 to do pseudo-random reads of the data, perhaps simply starting at the middle and alternating leftly equally. If the data is read in in sorted order, the result is a very long linked list, since each value will be greater than the one previous to it, and the lists will consist of all right pointers. There are techniques for "balancing" b-trees, to son confronted with data which is already sorted, or nearly sorted. This is because the tree works best (needs the fewest compares) when it is relatively "balanced", that is, the data is in random order and thus falls to the left and right branches relativeple /// Record Processing Services package uses a highly modified version of this technique as the basis for its 8-key access method. I say "highly modified" because the routine above, while it is fast and efficient for most data, has a severe problem wheo use this technique to create an access method which has several advantages over our "hash" algorithm from a previous episode. Its worth noting that variations on the b-tree scheme are the basis for sophisticated access methods on large machines. The Apnues until all values are exhausted and the return is taken at line 860. This one is worth working through too! To "B-tree" or not to "B-tree" Well, that concludes our look this time at binary sort techniques and b-trees. Next month we'll look at how tide of that branch is checked (line 840). It out of right branches also, the routine exits one level up, takes the top value off the stack (line 850), decrements the stackpointer and puts that record in the list (back at line 830). Then the process contiates the algorithm we studied earlier. First, it works its way down the left side of the tree, saving records on the stack as it goes. When the left-hand pointers finally run out, that record becomes the first entry in the list (line 830) and the right s recpntr=recpntr+o1:parray%(recpntr)=slist%(rec) 840 IF sortpointr%(rec) THEN rec=sortpointr%(rec):GOTO 820 850 IF stackpointer THEN rec=stack%(stackpointer):stackpointer= stackpointer-o1:GOTO 830 860 RETURN Notice how this routine duplicted pointer array "parray%". That routine looks like this: 800 parray%(0)=slist%(0) 810 recpntr=0:stackpointer=0:rec=1 820 IF sortpointl%(rec) THEN stackpointer=stackpointer+o1: stack%(stackpointer)=rec:rec=sortpointl%(rec):GOTO 820 830 i-1#.j<>midsarray(i),sarray(mid)8l=l+1:stack(l)=i:1010)B check for cases of 1 or 2 elementsLj-m<211303Vsarray(m)>=sarray(m+1)sarray(m),sarray(m+1)8` Set begin and end points and check for completion!jm=stack(l)+1:l=l-1:l>0emp=temp-span:temp>010608iBspan=span/2)Lspan1030:ۿinMOort" 1000,"Sort complete. First 10 elements are:"i=110:sarray(i);" ";: ::110 n<2span=n/2)newspan=n-spani=1newspan temp=i$upper=temp+spanY.sarray(temp)>sarray(upper)sarray(temp),sarray(upper):tH( sort using Shell-Metzner technique n is number of elements( sarray is the array to be sorted2dsarray(1000)9n"Sort Routine. Number of elements to generate: ";a$xn=a$):n<2200i=1n:sarray(i)=1):"Start of S+1))parray(i),parray(i+1):madeswap=1imadeswaptop=top-1:1010$ShJL$):n<2200(i=1n:sarray(i)=1):parray(i)=i:"Start of Sort" 1000,"Sort complete. First 10 elements are:"&i=110:sarray(parray(i));" ";: ::110 top=n-1madeswap=0 i=1topMsarray(parray(i))>sarray(parray(iR! sort using bubble technique N is number of elements( sarray is the array to be sorted2( parray is the pointers to the sorted array2dsarray(1000),parray(1000)9n"Sort Routine. Number of elements to generate: ";a$xn=adsarray(1000)9n"Sort Routine. Number of elements to generate: ";a$xn=a$):n<2200i=1n:sarray(i)=1):"Start of Sort" 1000,"Sort complete. First 10 elements are:"i=110:sarray(i);" ";: ::1101010troS6parray%(1000),sortpointl%(1000),sortpointr%(1000))slist%(1000),sort$(1000),stack%(200):"Prepare a sorted list"A:"Choose the beginning and ending columns to sort on: ";b,e9,b<1e290?U"Do you wish to sort using the existing sorted order? ";a$ZaU6 parray%(1000),sortpointl%(1000),sortpointr%(1000))slist%(1000),sort$(1000),stack%(200)z=0:o1=1:o2=2:"Prepare a sorted list""(:"Name of file to sort: ";a$-a$=""600D2a$)>11"Filenames must have a maximum of 11 characters"#1,"JUNKFILE",12&"NUMBER OF RECORDS TO CREATE: ";N I=1N A$="" J=15A$=A$+65+6*1)))#J &A$=A$+" " )K=14*A$=A$+48+10*1)))+K-A$ /#1,I;A$2I<F%(0):#2;parray%(i):j"Sorted list stored."nslist%(0)=rec%-1 1363ntr%)=slist%(rec%)$^rpointer%rec%=rpointer%:1368`stackpointer%rec%=stack%(stackpointer%):stackpointer%=stackpointer%-1:lpointer%=sortpointl%(rec%):rpointer%=sortpointr%(rec%):1372d:"Storing sorted list"f#2,0;parray%(0)%hi=1parrayparray%Sparray%(0)=slist%(0)%Urecpntr%=0:stackpointer%=0:rec%=1;Xlpointer%=sortpointl%(rec%):rpointer%=sortpointr%(rec%)]Zlpointer%stackpointer%=stackpointer%+1:stack%(stackpointer%)=rec%:rec%=lpointer%:13686\recpntr%=recpntr%+1:parray%(recpointer%=sortpointr%(testrec%)'Fsort$(rec%)>=sort$(testrec%)1356+Hlpointer%<>0testrec%=lpointer%:1348$Jsortpointl%(testrec%)=rec%:1360+Lrpointer%<>0testrec%=rpointer%:1348Nsortpointr%(testrec%)=rec% Prec%"R sort finished, build (1)=0:sortpointr%(1)=08ž#1slist%(0)=rec%-1:1363 9rec%=1&:#1,slist%(rec%);sort$(rec%):".";<rec%=2slist%(0) >#1,slist%(rec%);sort$(rec%) A".";6Btestrec%=1:sortpointl%(rec%)=0:sortpointr%(rec%)=0CDlpointer%=sortpointl%(testrec%):rpPRST0:1)<>21333d3"Do you wish to sort using the existing sorted order? ";a$:a$=a$,1,1):a$<>"y"a$<>"Y"133374#2;slist%(0):i=1slist%(0):#1;slist%(i)::1334+5slist%(0)=1000:i=11000:slist%(i)=i:6j=0200:stack%(j)=0:%7sortpointl%$=a$,1,1)o_a$<>"y"a$<>"Y"slist%(0)=1000:i=11000:slist%(i)=i:::#2;slist%(0):i=1slist%(0):#o2;slist%(i):ٟ630" sort finished, build parray%:ٟparray%(0)=slist%(0)""recpntr=0:stackpointer=0:rec=1f,sortpointlec::N3#o1,slist%(rec);a$:sort$(rec)=a$,b,ln):".";4sortpointl%(rec)=z:sortpointr%(rec)=z:testrec=o1 :"End of sort program."*:e rec=1:700rec=2slist%(0)700$sort$(rec)>=sort$(testrec)570<&sortpointl%(testrec)testrec=sortpointl%(testrec):540 0sortpointl%(testrec)=rec::<:sortpointr%(testrec)testrec=sortpointr%(testrec):540 Dsortpointr%(testrec)=ro1:310OٟT:"Storing sorted list"^#2,0;parray%(0)&hi=1parray%(0):#o2;parray%(i):r"Sorted list stored."6w"Print sorted records? ";a$:a$<>"y"a$<>"Y"400|i=1slist%(0):#o1,parray%(i);a$:a$::40ž#1slist%(0)=rec-1::%(rec)stackpointer=stackpointer+o1:stack%(stackpointer)=rec:rec=sortpointl%(rec):30036recpntr=recpntr+o1:parray%(recpntr)=slist%(rec)0@sortpointr%(rec)rec=sortpointr%(rec):300MJstackpointerrec=stack%(stackpointer):stackpointer=stackpointer-$=a$,1,1)o_a$<>"y"a$<>"Y"slist%(0)=1000:i=11000:slist%(i)=i:::#2;slist%(0):i=1slist%(0):#o2;slist%(i):ٟ500" sort finished, build parray%:ٟparray%(0)=slist%(0)""recpntr=0:stackpointer=0:rec=1f,sortpointl[]^_:40 7#1,a$A<:"Choose the beginning and ending columns to sort on: ";b,e7Ab<1e290?U"Do you wish to sort using the existing sorted order? ";a$Za6 parray%(1000),sortpointl%(1000),sortpointr%(1000))slist%(1000),sort$(1000),stack%(200)z=0:o1=1:o2=2:"Prepare a sorted list""(:"Name of file to sort: ";a$-a$=""800D2a$)>11"Filenames must have a maximum of 11 characters"ort$(rec)=a$,b,ln):".";4sortpointl%(rec)=z:sortpointr%(rec)=z:testrec=o1e 700$sort$(rec)>=sort$(testrec)670<sortpointl%(testrec)testrec=sortpointl%(testrec):655 sortpointl%(testrec)=rec::<sortpointr%(testrec)testrec=sortpointr%(testrec):655 sortpointr%(testrec)=rec::3#o1,slist%(rec);a$:srec=1:700rec=2slist%(0)700$stk=(sort$(rec)>=sort$(testrec))B&sortpoint%(stk,testrec)testrec=sortpoint%(stk,testrec):540!0sortpoint%(stk,testrec)=rec::DX:vž#1slist%(0)=rec-1::{rec=1:700rec=2slist%(0)o1:310OٟT:"Storing sorted list"^#2,0;parray%(0)&hi=1parray%(0):#o2;parray%(i):r"Sorted list stored."3w"Print sorted keys? ";a$:a$<>"y"a$<>"Y"400|i=1slist%(0):#o1,parray%(i);a$:a$::40ž#1slist%(0)=rec-1::%(rec)stackpointer=stackpointer+o1:stack%(stackpointer)=rec:rec=sortpointl%(rec):30036recpntr=recpntr+o1:parray%(recpntr)=slist%(rec)0@sortpointr%(rec)rec=sortpointr%(rec):300MJstackpointerrec=stack%(stackpointer):stackpointer=stackpointer-ž#1slist%(0)=rec-1::rec=1:700rec=2slist%(0)700$stk=(sort$(rec)>=sort$(testrec))B&sortpoint%(stk,testrec)testrec=sortpoint%(stk,testrec):540!0sortpoint%(stk,testrec)=rec::e _________________________________ | Open | ASCII | | Apple | Character Code | |_______|_________________________________________| Byte Two 7 was pressed. The layout of the two bytes looks like this (stolen from Appendix G of the Standard Device Drivers manual): Byte One 7 6 5 4 3 2 1 0 __________________s, is a "special" key within the SOS keyboard definition. That means that although a normal read to the console returns an ASCII 13 in both cases, a "two-byte" read will return a flag in the keyboard status byte that indicates whether or not a special keyf uses. Before getting into the program, let's consider some possible solutions and their problems. We have agreed that the only way to tell the difference is to use the fact that the ENTER key, along with the rest of the numeric pad and some other keyhen we delved into creating a data entry screen builder program. Rather than answer the question by inserting that specific capability into the previous program, the following is a general purpose keyboard read program which you can modify to any number ocefghijklmnopqrstuvwxolumn, we will now move swiftly from the sublime to the overly intense, and in the process answer last month's mystery question, "How can I tell the difference between RETURN and ENTER?". This question, or one at least similar to it, was asked last time w T H E T H I R D B A S I C Exploring Business Basic - Part 13 Last time we plunged as far as anyone seemed to dare into creating special screen functions. As is the usual pace in this cabARTICLE13v' '*ARTICLE.13d*+a,NOWAIT.2BYTE zSa6 5 4 3 2 1 0 _________________________________________________________________________ | Special| Kybd On| Closed | Open | Alpha | Control| Shift | Any | | Key | | Apple | Apple | Lock | Key | Key | Key | |________|________|________|_________|________|________|________|_______| Note: "Keyboard On" and "Any Key" will normally be "1" for any keypress, the other bits will be "1" only if that particular function is activea key is pressed, the ON KBD statement in 55 sends the program leaping to line 65. That routine looks like this: 65 OFF KBD 70 INPUT#1;char$ 75 PRINT:HPOS=24 77 FOR k=1 TO LEN(char$):PRINT" ";MID$(HEX$(ASC(MID$(char$,k,1))),3,2);:NEXT k 79 PRINT trigger to go and look at what was typed. Between keypresses, line 60 keeps something interesting going on the screen, to let you know that it's waiting. Obviously, the routine at line 60 could be expanded to do useful work (more on that later!). When for input, even to a "prompt". Line 55 starts the interesting stuff. We could, of course, keep reading the console until something showed up (sometimes called "polling" the keyboard). A better, more efficient way is to use the keyboard interrupt as aT. It sets up a jump to a "back to normal" routine. If for some reason your program terminates without setting everything back, BASIC will be VERY confused, and you will have to reboot. This is because BASIC, like everybody else, uses the console driver Line 40 performs the control call which puts the console into two-byte read mode. Line 45 puts the console into no-wait mode, so that read requests are immediately filled with whatever has been typed up to the point of the read. Line 50 is VERY IMPORTAN";:FOR j=1 TO 200:NEXT j:HPOS=HPOS-1:PRINT"\";:FOR j=1 TO 200: NEXT j:HPOS= HPOS-1:GOTO 60 Line 5 invokes the REQUEST.INV module, which performs SOS calls. Then lines 10 through 35 set up the screen and initialize variables which will be used later. s: ":HPOS=0:VPOS= VPOS-2 25 OPEN#1,".console" 30 hex80$=CHR$(128):hex00$=CHR$(0):clearendvp$=CHR$(29) 35 device$=".console" 40 PERFORM control(%3,@hex80$)device$ 45 PERFORM control(%10,@hex80$)device$ 50 ON ERR GOTO 100 55 ON KBD GOTO 65 60 PRINT"/0 with a parameter of 128 does just that. With that, lets look at a program to monitor the keyboard and print out the two-byte code for whatever is typed. 5 INVOKE"request.inv" 10 HOME 15 PRINT"Test two byte reads" 20 PRINT:PRINT"Input buffer containon. How can you do it? The most plausible answer lies in yet another console capability, the little known "no-wait read". We want to read all the characters in the input buffer, anytime, without a termination character. The console control call number 1l string to the user, no matter what was typed. Interestingly enough, the ASC function interprets a null string as having an ASCII value of -1 (You may have to try that one to believe it). Now that all that's clear, lets get back to the original questithe console of this desire. The console keeps getting two characters each keypress, and for reasons too bizarre to discuss here, returns no characters to the GET. GET, expecting always to receive a character as soon as one is typed, bravely returns a nule terminator character (normally RETURN) and when two bytes keep popping up, INPUT gets confused. Ok, still simple, let's do a GET, which doesn't require a terminator character. Oops again. GET expects to receive only one character, and in fact informs ith a parameter of 128 (hex 80) the console will return two bytes for each keypress. OK! Now all we have to do is perform the control call and input from the console, right? Unfortunately, ordinary reads don't work too well. They keep expecting the lin. Well, this looks deceptively simple. We learned in a previous episode that there is a control call to the .CONSOLE driver (using REQUEST.INV) which can put the keyboard into two byte read mode. This is device control call number 3. After calling it w clearendvp$:PRINT 80 IF char$=CHR$(9)+CHR$(199) THEN 100 85 HPOS=0:VPOS= VPOS-3 90 ON KBD GOTO 65 95 RETURN First, we turn off keyboard interrupts, and then perform a file INPUT statement to get the accumulated characters. Using file INPUT (INPUT#) is unusual, since it is generally easier to use the value of KBD, the reserved value containing the ASCII value of the keypress that triggered ON KBD. However, if you think that a two-byte read confuses GET, it really blows KBD's mind. Note that an ordintions to really simplify things for the user. An added benefit, and possibly the subject of a full article one of these days, it the ability to do useful work within an application while waiting for the user to type on the keyboard. One piece of useful ws you more than you could possibly want to know about almost everything. In the case of two-byte reads, the result of knowing all this stuff is the possiblity of developing some really friendly applications which use the keypad and other keystroke combinakspace. Applewriter /// uses "control-backarrow" (a "08 C5") for deleting a character and a simple backarrow ("08 C1") for cursor movement. With single byte reads, these two different combinations would be indistinguishable. As usual, this column tellow programs like "Word Juggler" can use the numeric pad as a special function key set. Another handy example is "control-H" is a "08 45" while the backarrow key is "08 C1". This allows a program to distinguish between an ASCII backspace, and a cursor bacy" flags set. ENTER, on the other hand, is read as "0D C1" which means carriage return with "keyboard on", "any key" and "special key" flags set. Similarly, a "1" on the main keyboard is read as "31 41", while "1" on the numeric pad is "31 C1". That's hendless. When you are tired of trying out all the weird combinations (like Open-Apple, Closed-Apple, Control, Shift, Alpha Lock A), then consider the more useful ones. RETURN is read as "0D 41" which means carriage return with "keyboard on" and "any kee jewel, you can try some interesting things. So far I've discovered ten different variations on the letter "a", and there are bound to be more. For those who think the Apple /// only has two function keys, think again! The combinations are practically clean things up and terminate the program. They look like this: 100 REM return to reality 105 PERFORM control(%3,@hex00$)device$ 110 PERFORM control(%10,@hex00$)device$ 115 PRINT:PRINT 120 CLOSE:INVOKE 125 END Now that you've typed in this littleystrokes just by substituting the appropriate character codes in line 80. If the characters don't match, the routine resets the display location and returns to the little timewaster in line 60. In the event of a match (or an error), line 100 through 125 of the program, by testing for a certain two-byte combination. Checking your keyboard chart should prove that the desired combination (read as an ASCII 9 followed by an ASCII 199) is a "control-shift TAB". You can change the exit to any combination of k or less - FF in hex). Line 79 prints the console command to "clear to end of viewport" (CHR$(29)). This insures that if the previous display contained multiple characters, the excess ones will be erased when the PRINT occurs. Line 80 gives us a way out the somewhat complex print statement in line 77 throw you. It is simply taking each character, converting it to its ASCII equivalent, converting that to hexadecimal notation, and then extracting the rightmost two Hex digits (since the value is always 255gh the string, and prints out the HEX codes of each character there. By scanning the entire length of the string, we take care of the case where rapid typing managed to enter characters while the previous set of characters were being processed. Don't letary INPUT statement with no-wait on only returns the first byte in the buffer (don't ask me why!). Thus we use INPUT#, and since no-wait is turned on, we'll get any characters currently in the buffer placed in the variable "char$". Line 77 scans throuork would be to print out a disk file which was previously written by the application. You could use the driver status calls which tell how many characters are left to be printed by a printer driver and occasionally print enough to keep the printer busy. In some circles, this is known as "spooling" and is considered really tricky. With SOS and the input routine above, it becomes relatively trival. Good luck, and have fun! P.S.: Next month's column is a long treatise on sorting techniques in Basic, incl! sort using bubble technique n is number of elements( sarray is the array to be sorted2 dtop=n-1nhaveswapped=0 xi=1top@sarray(i)>sarray(i+1)sarray(i),sarray(i+1):haveswapped=1i haveswappedtop=top-1:110ray(i+1):madeswap=1imadeswaptop=top-1:1010$>! sort using bubble technique N is number of elements( sarray is the array to be sorted2( parray is the pointers to the sorted array2 top=n-1madeswap=0 i=1topMsarray(parray(i))>sarray(parray(i+1))parray(i),par|+TIME.INVERT |T|)QUICKSORT |SHELL.SORT U|SHELL.SUB |%SHELL J|SIMPLE.BUBBLE |SORT.FRAME  |}ARTICLE14v' '*ARTICLE.146i|BUBBLE.SORT /|BUBL.PNTR.SORT ~@ |+INVERT.SORT |*LONG.QUICK _!/|+PNTR.BUBBLE control(%3,@hex00$)device$ ncontrol(%10,@hex00$)device$s:x:}y{ 765;9<"/";:j=1200:j:=-1:"\";:j=1200:j:=-1:60=A F#1;char$ K:=245Mk=1char$):" ";char$,k,1))),3,2);:kOclearendvp$:Pchar$=9)+199)100 U=0:=-3 Z65_d return to realityiQ".d1/request.inv" "Test two byte reads"*:"Input buffer contains: ":=0:=-2#1,".console"/hex80$=128):hex00$=0):clearendvp$=29)#device$=".console"(control(%3,@hex80$)device$ -control(%10,@hex80$)device$ 2œ100uding some routines that can sort a thousand items in a minute or so. Unbelieveable? Watch this space! Until then, a final puzzle. What combination of keys creates the largest value for a two-byte read (considering both bytes as one 16 bit unsigned vaS( sort using Shell-Metzner technique n is number of elements( sarray is the array to be sorted2 n<2span=n/2)newspan=n-spani=1newspan temp=i$upper=temp+spanY.sarray(temp)>sarray(upper)sarray(temp),sa% Initialize begin and end pointsl=1:stack(1)=n+1:m=1j=stack(l):i=m-1j-m<31100mid=(i+j)/2)4i=i+1:i=j1060::sarray(i)<=sarray(mid)1040Kj=j-1:i<>jsarray(j)=midi= dsarray(1000),stack(100)9n"Sort Routine. Number of elements to generate: ";a$xn=a$):n<2200i=1n:sarray(i)=1):"Start of Sort" 1000,"Sort complete. First 10 elements are:"i=110:sarray(i);" ";: ::110p)>value$(upper)value$(temp),value$(upper):spointer%(temp),spointer%(upper):temp=temp-span:temp>04050ispan=span/2)span4020::;ad":  last=j+1& i 0 4000: k=0 D i=1nN index=spointer%(i)EX k=k+1:sarray%(k)=index:pntr%(index)<>0index=pntr%(index):3160v i n<2span=n/2)newspan=n-spani=1newspan temp=iupper=temp+span{value$(temn=1255 x=sortval$,zero$) x=03050 value$(n)=sortval$,x-1) sortval$=sortval$,x+1) n value$(n)=sortval$ last=1 i=1n j=last2555 startval%(j)<>0spointer%(i)=startval%(j):3100 j" "ERROR, startval not founx=sortval$,item$)0xpntr%(endval%(x))=rec%:endval%(x)=rec%:x=sortval$)+29x+item$)>255"sortval$ overflow, sort aborted":!sortval$=sortval$+zero$+item$startval%(x)=rec%endval%(x)=rec%  sortval$=sortval$,2) ec% 3000S"Number of unique values: ";n:"Ratio of total records to unique values: ";a/n,"Press return for the sorted list: ";a$'i=1a:array$(sarray%(i));" ";:i$::"Sort the array again? ";a$%a$=a$,1,1):a$="Y"a$="y"1001)*26)+65)Zj,idi=1a:array$(i);" ";:i1::"Start and end columns for sort: ";c1,c2Li=1a:pntr%(i)=zero%::i=1255:startval%(i)=zero%:endval%(i)=zero%:sortval$=""rec%=1a$item$=array$(rec%),c1,c2-c1+1) 2000 rI pntr%(1000),startval%(255),endval%(255),spointer%(255),sarray%(1000)zero$=0):zero%=0array$(1000),value$(128)(("Number of strings to generate: ";a*2"Number of characters per string: ";b <i=1a Fj=1b(Parray$(i)=array$(i)+rray(upper):temp=temp-span:temp>010608iBspan=span/2)Lspan1030ۿi-1#.j<>midsarray(i),sarray(mid)8l=l+1:stack(l)=i:1010)B check for cases of 1 or 2 elementsLj-m<211303Vsarray(m)>=sarray(m+1)sarray(m),sarray(m+1)8` Set begin and end points and check for completion!jm=stack(l)+1:l=l-1:l>01)*26)+65)Zj,idi=1a:array$(i);" ";:i1::"start and end columns for sort: ";c1,c2Li=1a:pntr%(i)=zero%::i=1255:startval%(i)=zero%:endval%(i)=zero%:sortval$=""ٟrec%=1a$item$=array$(rec%),c1,c2-c1+1) 2000 I pntr%(1000),startval%(255),endval%(255),spointer%(255),sarray%(1000)zero$=0):zero%=0array$(1000),val$(128)(("number of strings to generate: ";a*2"number of characters per string: ";b <i=1a Fj=1b(Parray$(i)=array$(i)+ n<2span=n/2)newspan=n-spani=1newspan temp=iupper=temp+spanYsarray(temp)>sarray(upper)sarray(temp),sarray(upper):temp=temp-span:temp>04050ispan=span/2)span4020ۿ610R:"Start time is: ";:j=1n:sarray(j)=1):::"Start of sort time is: ";tart):rightstack%(start)=i-1:3406leftstack%(start+1)=i+1+@rightstack%(start+1)=rightstack%(start)Jrightstack%(start)=i-1Tstart=start+1^130i=110:sarray(i),:"Stop time is: ";Xb"Qsort. N=";n n=0=i+1:230 j=j-1*i=(rightstack%(start)-i)leftstack%(start+1)=i+1:rightstack%(start+1)=rightstack%(snt>sarray(midpoint))220(sarray(i)pivotpoint)(sarray(i)>sarray(midpoint)sarray(i)=j280%sarray(i)=rightstack%(start)start=start-1:start>0130::,i=leftstack%(start):j=rightstack%(start)pivotpoint=sarray(j)midpoint=(i+j)/2)j-i<6200r(pivotpoint>sarray(i)pivotpointsarray(parray(iR! sort using bubble technique N is number of elements( sarray is the array to be sorted2( parray is the pointers to the sorted array2dsarray(1000),parray(1000)9n"Sort Routine. Number of elements to generate: ";a$xn=adsarray(1000)9n"Sort Routine. Number of elements to generate: ";a$xn=a$):n<2200i=1n:sarray(i)=1):"Start of Sort" 1000,"Sort complete. First 10 elements are:"i=110:sarray(i);" ";: ::1101000,"Sort complete. First 10 elements are:"i=110:sarray(i);" ";: ::110 top=n-1madeswap=0 i=1top=sarray(i)>sarray(i+1)sarray(i),sarray(i+1):madeswap=1imadeswaptop=top-1:1010$! sort using bubble technique n is number of elements( sarray is the array to be sorted2dsarray(1000)9n"Sort Routine. Number of elements to generate: ";a$xn=a$):n<2200i=1n:sarray(i)=1):"Start of Sort" n<2span=n/2)newspan=n-spani=1newspan temp=iupper=temp+spansval$(temp)>val$(upper)val$(temp),val$(upper):spointer%(temp),spointer%(upper):temp=temp-span:temp>04050ispan=span/2)span4020::;astartval%(j):3100 j" "error, startval not found":  last=j+1& i+ "sort val$: " 0 4000: k=0? "build sarray: " D i=1nN index=spointer%(i)EX k=k+1:sarray%(k)=index:pntr%(index)<>0index=pntr%(index):3160v i l$,2) "build val$: " n=1255 x=sortval$,zero$) x=03050 val$(n)=sortval$,x-1) sortval$=sortval$,x+1) n val$(n)=sortval$ "build spointer: " last=1 i=1n j=last2555 startval%(j)<>0spointer%(i)=100x=sortval$,item$)0xpntr%(endval%(x))=rec%:endval%(x)=rec%:x=sortval$)+29x+item$)>255"sortval$ overflow, sort aborted":!sortval$=sortval$+zero$+item$startval%(x)=rec%endval%(x)=rec%  sortval$=sortvaH( sort using Shell-Metzner technique n is number of elements( sarray is the array to be sorted2dsarray(1000)9n"Sort Routine. Number of elements to generate: ";a$xn=a$):n<2200i=1n:sarray(i)=1):"Start of Sf the key ingredients of most business and scientific programs which handle large amounts of data is the ability to arrange that data in an ordered sequence. "Arrange data in an ordered sequence" is, of course, a windy way of saying "sorting". There are (embarrassingly well documented compared to the abbreviated listings normally foisted upon you in this column). For being first with a valid solution, Arnold wins a copy of "Quickfile ///" and the thanks of a grateful nation. Sorting it all out One otage of the fact that the console still terminates a read on an ASCII 13 (which Return and Enter both generate) and correctly identified that an INPUT# statement was required to correctly read both bytes. It was an excellent solution, and well documented the challenge itself could be issued (maybe a better name for the column is Tommorrowland!) The published solution relied heavily on the desire to read a keystroke at a time and sample each one, simulating the "GET" statement in BASIC. Arnold took advanience, to come up with a routine in Business Basic that allowed a program to tell the difference between the "Enter" and "Return" keys. Last month a solution was furnished in this column, but because of publishing lead times, it had to be submitted beforete Arnold Bailey of New York State for his intrepid solution to our challenge on two-byte reads from the console. For those who missed the last two cliff-hangers, we wrapped up the discussion of nifty ".console" features by challenging you, the studio aud T H E T H I R D B A S I C by Taylor Pohlman Welcome back to Basicland. Before we plunge waist-deep into today's exciting episode, it's time to congratulaemp=temp-span:temp>010608iBspan=span/2)Lspan1030:ۿinort" 1000,"Sort complete. First 10 elements are:"i=110:sarray(i);" ";: ::110 n<2span=n/2)newspan=n-spani=1newspan temp=i$upper=temp+spanY.sarray(temp)>sarray(upper)sarray(temp),sarray(upper):tas many sort techniques as there are people to think them up, but for the purposes of this column and its Christmas (December) cousin, we will stick to four or five fundamental methods. Business Basic has several nifty features which make sorting more efficient, and the huge memory space makes sorting large collections of strings or numbers practical to do in memory. For that reason, "Merging", the flip side of most sort techniques, will only be briefly covered in the December issue. For now we'll stick statement, and the result is that you have to assign one value to a temporary variable, do the reassignments, etc. This also costs a lot of time, whereas SWAP is very fast. As long as things are being noted, its probably worth pointing out that this rou to the top of the list (try it out if you don't believe it). This is something that some versions of this routine miss, and it leads to a lot of unnecessary scanning. Notice also that we take advantage of the SWAP statement. Some Basics don't have thisf not, the array might not be in order, and needs to be processed again until all possible swaps have been made. Notice that line 1050 resets "top", the limit of checking, since after each pass the largest number found anywhere in the array will be forcedanion, until the top is reached. Note that's why "top" is set equal to the number of elements minus one. When a complete scan is made, line 1050 checks to see if any swaps were made in the last pass. If there were not, then the array must be in order. IIf the first element is greater than the second, then the SWAP statement is used to exchange them, and the "madeswap" flag is set to show that an exchange was made. Then the process repeats with that next higher element compared with its next highest comp010 establishes a variable "madeswap" which is a flag we'll use later to determine if more sorting needs to be done. That leads us the the main routine in lines 1120 to line 1040. Line 1030 scans each element and compares it to the next higher element. ten elements (just to demonstrate that the data is sorted) and end. Remember that this framework is deliberately simple, so we can concentrate on the sort technique itself. Line 1000 establishes the upper limit of our check for correct order and line 1 of testing it, lines 110 through 140 ask for the number of elements to sort, load "sarray" with random values, and then call the subroutine at line 1000 to perform the actual sort. After returning from the sort, lines 150 through 200 print out the first 0 FOR i=1 TO top 1030 IF sarray(i)>sarray(i+1) THEN SWAP sarray(i),sarray(i+1):madeswap=1 1140 NEXT i 1050 IF madeswap THEN top=top-1:GOTO 1010 1071 RETURN This first program sorts a numeric array, rearranging it in memory. For purposesR i=1 TO n:sarray(i)=RND(1):NEXT 135 PRINT"Start of Sort" 140 GOSUB 1000 150 PRINT"Sort complete. First 10 elements are:" 160 FOR i=0 TO 10:PRINT sarray(i);" ";:NEXT 170 PRINT:PRINT:GOTO 110 200 END 1000 top=n-1 1010 madeswap=0 102 REM sort using bubble technique 20 REM n is number of elements 30 REM sarray is the array to be sorted 50 REM 100 DIM sarray(1000) 110 INPUT"Sort Routine. Number of elements to generate: ";a$ 120 n=CONV(a$):IF n<2 THEN 200 130 FOed. For the purposes of the sample program, and all the rest of the programs in this series, we will assume the desire is to sort the lists in "ascending" (lowest to highest) order. Let's plunge into the first example to illustrate how this works: 10 nd shuffling (bubbling) it up to its proper place in the list, it depends on comparing each value to the one next door, and exchanging them if the order is wrong. If you compare each element with its neighbor enough times, eventually the list will be sortto in-memory sorts to illustrate the techniques. I'm forever showing bubbles The most common sorting technique, and the one guaranteed to show up in every elementary textbook, is the "bubble" sort. So named because of the technique of taking a value atine could be rewritten for descending order by rearranging the sense of the IF statement and having the search go from a varying "bottom" to a fixed "top" instead of the other way around. One last comment. Although this sort technique is the simplest, it is also the slowest, except for those situations where the list is very short or almost sorted already. Simple timing tests will convince you that the routine slows down non-linearly as the size increases. "Non-linearly" means it goes from "bad" to "aw The PRINT statement in line 160 illustrates how the pointer array is used to look up the correct sequence of values, even though they are actually scattered around within "sarray". One of the interesting possibilities of this technique is that it is pos parray%(i) and parray%(i+1) as pointers to where the real data is. Once the comparisons are made, the pointers (not the values themselves) are physically exchanged. When the sort is finished, the routine returns to line 150 to print out the sorted data.y, it can be declared an integer array (maximum value 32767) to save space. Having set all the values up, the GOSUB to line 1000 sorts the data, as in the first example, except now in line 1030, instead of testing the actual sarray values directly, we usesent the sorted order. Notice in line 130 that "parray" is set initially to the sequence 1, 2, 3 ..., the same sequence as we find the data in "sarray" initially. However, since the pointer array contains only references to locations in the original arra parray%(i+1):madeswap=1 1040 NEXT i 1050 IF madeswap THEN top=top-1:GOTO 1010 1060 RETURN Notice that we have introduced a new array in the program at line 100. "Parray" contains the pointers to the actual locations in "sarray" which represt 10 elements are:" 160 FOR i=1 TO 10:PRINT sarray(parray%(i));" ";:NEXT 170 PRINT:PRINT:GOTO 110 200 END 1000 top=n-1 1010 madeswap=0 1020 FOR i=1 TO top 1030 IF sarray(parray%(i))>sarray(parray%(i+1)) THEN SWAP parray%(i), rray(1000),parray%(1000) 110 INPUT"Sort Routine. Number of elements to generate: ";a$ 120 n=CONV(a$):IF n<2 THEN 200 130 FOR i=1 TO n:sarray(i)=RND(1):parray%(i)=i:NEXT 135 PRINT"Start of Sort" 140 GOSUB 1000 150 PRINT"Sort complete. First program to incorporate this "pointer sort" technique: 10 REM sort using bubble technique 20 REM N is number of elements 30 REM sarray is the array to be sorted 40 REM parray is the pointers to the sorted array 50 REM 100 DIM saSome languages and systems make a great deal out of this pointer concept. For now, the suggestion should be to use the technique wherever it makes sense from a performance, storage and convenience standpoint. The example below is an adaptation of the fir Such numbers are called "pointers", since the value that is listed is just a pointer to the actual data location, not the data itself. If you want to construct the sorted list of names, it is easy to look them up using the record number (pointer) list. ould represent that same sequence by listing the "record numbers": 4 2 5 3 1 Not only is this second representation more compact, it may actually represent less work to rearrange the "record numbers" than the actual data itself. Gloria 4 Alphonse 5 Gaston One way to sort this list is to simply arrange it in the following sequence: Alphonse, Bill, Gaston, Gloria, Henry That's ascending alphabetical order. However, we cribes what the order would be if they were physically sorted. For example, consider the following list (as it might have been read from a file): Record number Item 1 Henry 2 Bill 3 ful" without passing through "worse". Getting the point The routine below is another variation on the bubble theme, with one important exception. Sometimes it doesn't make sense to actually rearrange the data, but rather only to create a list that descsible to have more that one pointer array to a given data array. In that way, you could have an "sarray" which had associated with it a "parrayup" and a "parraydown" pointer array, so that listings and searches could be done in either order, depending on the program requirements, without resorting. These are sometimes called indexes, and are very useful in many applications. More typically, pointer arrays are used in situations where the original array consists of string values, or fields within disk rec the span is one, at which point the array is sorted. Line 1100 checks for that happy occurrence, and returns if it is so. Several things are worthy of note in this routine. First, the Shell sort will work faster on sorted data than unsorted data, and of this pivot point. After each swap, the range of search is narrowed by decreasing the "temp" value in line 1070, and the process is repeated. After each major pass with a span value, line 1090 cuts the span value in half and repeats the process, untilaper and follow through on exactly how this routine works for your own satisfaction. Briefly, the "newspan" variable establishes a pivot point with the loop in lines 1040 through 1080 facilitating conparisons and swaps between the upper and lower portionsine 1000 that there is more than one element to be sorted. Once that is established, line 1010 divides the array into halves, and line 1030 established the center point around which swaps will be made. It would be a good idea to create a small array on pINT(span/2) 1100 IF span THEN GOTO 1030:ELSE RETURN As you can see, the initial routine from line 10 to 200 is essentially the same as in the other routines. The subroutine in line 1000 implements the Shell algorithm, starting with a quick check in l1010 span=INT(n/2) 1030 newspan=n-span 1040 FOR i=1 TO newspan 1050 temp=i 1060 upper=temp+span 1070 IF sarray(temp)>sarray(upper) THEN SWAP sarray(temp),sarray(upper):temp=temp-span:IF temp>0 THEN 1060 1080 NEXT i 1090 span=0 130 FOR i=1 TO n:sarray(i)=RND(1):NEXT 135 PRINT"Start of Sort" 140 GOSUB 1000 150 PRINT"Sort complete. First 10 elements are:" 160 FOR i=1 TO 10:PRINT sarray(i);" ";:NEXT 170 PRINT:PRINT:GOTO 110 200 END 1000 IF n<2 THEN RETURN 10 REM sort using Shell-Metzner technique 20 REM n is number of elements 30 REM sarray is the array to be sorted 50 REM 100 DIM sarray(1000) 110 INPUT"Sort Routine. Number of elements to generate: ";a$ 120 n=CONV(a$):IF n<2 THEN 20ing technique), it depends on long distance comparisons and swaps to speed up the sorting process. For simplicity, we'll look at the Shell sort in standard form, without the additions for pointer sorting. A typical Shell sort routine looks like this: stances, and thereby cut down the number of compares and exchanges necessary. A sorting algorithm called the Shell-Metzner sort accomplishes this. Usually called the "Shell Sort" (which is appropriate considering its similarity to the "shell game" switchhat is possible. That means that for a small value to get from the top to the bottom on an ascending sort requires lots of exchanges (one for each value in the array). One quasi-obvious way to speed this process up is to make comparisons across larger diderstand, but they are painfully slow on any reasonable amount of data. The fundamental problem is that the algorithm requires lots of comparisons, and even if the comparison indicates that the data needs to be moved, a move of one cell at a time is all tich are particularly suited to disk lookups. These techniques are sometimes generalized under the heading "access methods". A Mild Speed Lift All this is fine, but the original warning still is worth considering. Bubble sorts are simple and easy to unords. There the economy of storage of an integer array is very valuable, and the pointers consist of actual disk record numbers, which are then easy to look up in any specified order. Next time we will consider some sort techniques and pointer arrays wh in nearly every unsorted case, will out-perform the bubble sort, dramatically so in cases above fifty to one hundred values. Also, this routine can easily be adapted to a pointer sort, using the techniques outlined in the second example above. String arrays can be sorted simply by replacing the numeric comparison in line 1070 with a string array compare. The SWAP statement works equally well with string and numeric data. Sort of a new way to sort The algorithms above are fairly standard and safe, ao%:endval%(i)=zero%:NEXT 133 sortval$="" 140 FOR rec%=1 TO a 150 item$=MID$(array$(rec%),c1,c2-c1+1) 160 GOSUB 2000 170 NEXT rec% 190 GOSUB 3000 196 PRINT"Number of unique values: ";n:PRINT"Ratio of total records to unique values80 array$(i)=array$(i)+CHR$(INT(RND(1)*26)+65) 90 NEXT j,i 100 FOR i=1 TO a:PRINT array$(i);" ";:NEXT i 130 PRINT:PRINT:INPUT"Start and end columns for sort: ";c1,c2 132 FOR i=1 TO a:pntr%(i)=zero%:NEXT:FOR i=1 TO 255:startval%(i)=zertartval%(255),endval%(255),spointer%(255),sarray%(1000) 20 zero$=CHR$(0):zero%=0 30 DIM array$(1000),value$(128) 40 INPUT"Number of strings to generate: ";a 50 INPUT"Number of characters per string: ";b 60 FOR i=1 TO a 70 FOR j=1 TO b y it initially by sorting only on the first column. This will guarantee that there are only 26 unique values (since the routine generates only upper-case letters), no matter how many strings you generate. First, the main routine: 10 DIM pntr%(1000),st them. This is especially true if the number of unique values is much smaller that the total number of values. The program below lets you generate random string arrays, with up to a thousand values, and then pick a group of columns on which to sort. Trontain the same information, but in a considerably different form. Note also that the inverted list is assembled in ascending alphabetical order. That's not necessary to the example, but once the unique values are established, it is generally easy to sor MD Then the inverted list of this data would look like this: Value Locations CA 1, 3, 6 MD 2, 7 MO 5 NY 4 The two representations c State 1 CA 2 MD 3 CA 4 NY 5 MO 6 CA 7 u can think of it as a list of all the unique values in another list, with sublists which contain the record numbers of all the records sharing the same field value. In our example above, if we had the following situation: Record number t". Inverted lists are a favorite topic around "access method" and "database" experts, but the same principles can apply to sorts. Basically, an inverted list is not an upside down version of a regular list, as you might expect from the title. Rather, yoted as a two character code. There are many other examples, but that is one which is easy to imagine. The program below generates random string values, and then lets you test the practicality of a sort technique based on a concept called an "inverted lisly a few unique values among all the records. One classic example is sorting address records on the basis of the value of a "State" field. Obviously, there may be thousands of records to sort, but there are only 50 possible states, each typically represenuld be. Although the Apple /// is one of the fastest personal computers around, its not exactly a Cray I, so lots of times it pays to be able to pull tricks like the one below. Imagine a situation where there are lots of records to sort, but there are onnd will reliably sort any kind of data. Sometimes in application programs we are more fortunate, and can have special knowledge of what kind of data we are sorting. This allows for special techniques which are faster than any "general purpose" routine co: ";a/n 197 INPUT"Press return for the sorted list: ";a$ 200 FOR i=1 TO a:PRINT array$(sarray%(i));" ";:NEXT i 210 PRINT:PRINT:INPUT"Sort the array again? ";a$ 220 a$=MID$(a$,1,1):IF a$="Y" OR a$="y" THEN 100 230 END Lines 10 and 20 set up a lot of values which will be used later, while line 30 sets up the main string array "array$" and the array which will contain unique values "value$". After prompting for the number of strings and the size of each string, lines 60 through 90 build the st and ending pointers are established in the appropriate arrays. At this point "pntr%" contains a linked list for each unique value of "sortval$", with the starting point of the list pointed to by the appropriate element of "startval%" and the end point de the "zero$" spacer, and 2040 and 2050 establish start and end locations for this new value and then return to get the next record. This process continues until all the records are examined and all unique values are added to "sortval$" and their beginningue, and line 2025 checks to see if there is room for the value. You could probably come up with something more friendly than the "STOP" statement to solve the problem. In any case, if there is room, line 2030 adds the value to the "sortval$" string, withvalue was found, the routine's work is finished for now, and it returns to look at the next value. If the item was not found in "sortval$", then that means that is is a new unique value. Line 2020 gets the next possible location for storing the new valans that the next occurence of that value gets automatically put into the location in "endval%" that matches the start location of the value in "sortval$". This is another good one to try on paper until you get a feel for how it works. Assuming that the ray by putting the current record number into the space reserved for the last record number in the list of that unique value. That is, "endval%" is an array which remembers the index in "pntr%" of the last occurence of any particular unique value. That me none of the values contain an ASCII 0 themselves. Once INSTR either finds or doesn't find the "item$" value in "sortval$" the rest of the routine is set into motion. In the case that "item$" already exists in "sortval$", line 2010 updates the "pntr%" arc% 2060 RETURN This routine makes good use of the INSTR function, to search a string called "sortval$". Sortval$ contains all the unique values, separated by the ASCII value 0. This means that the unique values can easily be identified, assuming thatm$) 2010 IF x THEN pntr%(endval%(x))=rec%:endval%(x)=rec%:RETURN 2020 x=LEN(sortval$)+2 2025 IF x+LEN(item$)>255 THEN PRINT"sortval$ overflow, sort aborted":STOP 2030 sortval$=sortval$+zero$+item$ 2040 startval%(x)=rec% 2050 endval%(x)=recreates the sorted pointer list in "sarray%" and returns to lines 196 through 220 to print the list on demand and start the process over, if desired. Lets look now at the routine that creates and adds to the inverted list: 2000 x=INSTR(sortval$,iteor that record. The routine at line 2000, which we will examine shortly, adds the current value to the list of unique elements if necesary, and inserts its record number in the general list. The subroutine at line 3000 then orders the unique value list, chosen, line 132 and 133 initialize values and prepare for the main sort loop in lines 140 through 170. Note that for each record (rec%) to be sorted, the variable "item$" contains the value extracted from the main record which will become the sort key fn, since the unique combinations possible in sets of more than one column are probably too great for the routine to work properly. Note that in a controlled (non-random) set, like the States, this might not be a problem. In any case, once the columns arering array by randomly creating character strings composed of upper-case ASCII characters. Line 100 prints out the created array, and line 130 requests the columns on which to sort. Unless you create very few strings, it is best to sort on only one columfined by a zero in the location pointed to by "endval%". Now the fun begins. Having assembled the list of pointers to all values, it is necessary to sort the unique values themselves into the appropriate order, and then assemble the individual linked lists into a total sorted list. The routine to break out the unique values and sort them looks like this: 3000 sortval$=MID$(sortval$,2) 3002 FOR n=1 TO 255 3005 x=INSTR(sortval$,zero$) 3010 IF x=0 THEN 3050 3015 value$(n)=LEFT$(sortval$of sorts at least a hundred times faster. Many times it's not how much code is in the program, but how many times it must be executed which really makes the performance difference. For that reason, in sorting as well as any other activity, it really pays in order. It's hard to believe that the original "bubble sort" program in the beginning of this tome can be so short and so simple and yet take the longest to execute, while the last program, which seems so complicated and so long can do certain types ned by "spointer%" we guarantee that the whole list is in order. Glancing way back up to the original main program, you can see that line 196 through 220 can now take the "sarray%" list as a pointer list into the original records and print out that listtr%(index)<>0 THEN index=pntr%(index):GOTO 3160 3190 NEXT i 3200 RETURN Notice that each linked list starts with an index in "spointer%" and ends when the value in "pntr%" is zero. By assembling the lists one by one in the sorted sequence determivalues in the "spointer%" array, and loading all the elements of the linked lists in the new order. That will finish the subroutine, and looks like this: 3130 k=0 3140 FOR i=1 TO n 3150 index=spointer%(i) 3160 k=k+1:sarray%(k)=index:IF pnue values among them. As long as we have lots more records than unique values, this routine will save significant amounts of time. Anyway, to finish up, once the values are sorted, we can assemble the whole list of record pointers by following the start e only change is that we are sorting string data, and in addition to swapping the string values, we swap the "spointer%" values as well. The important thing here is that even though we may have processed thousands of records, we only have to sort the uniqemp),value$(upper):SWAP spointer%(temp),spointer%(upper) :temp=temp-span:IF temp>0 THEN 4050 4070 NEXT i 4080 span=INT(span/2) 4090 IF span THEN GOTO 4020:ELSE:RETURN That's right, campers, its our old friend (of a page ago) the Shell sort. Thto match. That's done in the subroutine at line 4000: 4000 IF n<2 THEN RETURN 4010 span=INT(n/2) 4020 newspan=n-span 4031 FOR i=1 TO newspan 4040 temp=i 4050 upper=temp+span 4060 IF value$(temp)>value$(upper) THEN SWAP value$(th string is built in "spointer%" by lines 3055 through 3110. This leaves us with a list of the actual values, and the beginning values of the linked list for each. Now all that remains is to sort the values themselves, and rearrange the "spointer%" list a given string value than a FOR-NEXT loop plowing through "value$", and since that operation has to be done for each record, speeding up the search was a critical issue. In any case, once "value$" is built, a corresponding list of the start values for eacero$" as a delimiter between values in "sortval$". One note here may help understanding. The whole reason why "sortval$" was used instead of going with a string array for the values was because INSTR is an infinitely (nearly) faster way of searching for NT"ERROR, startval not found":STOP 3100 last=j+1 3110 NEXT i 3120 GOSUB 4000 Lines 3000 through 3050 scan the "sortval$" array and break out each value into a separate element of "value$" for ease of lookup and sorting later. It relies on "z,x-1) 3020 sortval$=MID$(sortval$,x+1) 3025 NEXT n 3050 value$(n)=sortval$ 3055 last=1 3060 FOR i=1 TO n 3070 FOR j=last TO 255 3080 IF startval%(j)<>0 THEN spointer%(i)=startval%(j):GOTO 3100 3090 NEXT j 3095 PRI to examine your loops and repetitive code, and to think of the best algorithms possible. Remember too, that the last program is useful only if there are only a few unique values, and the safest bet in the general case is the Shell sort. Next time we will take up some other interesting sort techniques, including an improved Shell sort called the "Quick sort" and a completely different sort called the "Binary tree sort". The Binary Sort, like the inverted list sort discussed in this article, can also be t~240:=24:=0:"@ ..... "DATE.TIME.LINE" ....JM=Ҡ,4,2))BTM1630,1640,1650,1660,1670,1680,1690,1700,1710,1720,1730,1740^M$="JANUARY":1750hM$="FEBRUARY":1750rM$="MARCH":1750|M$="APRIL":1750M$="MAY":1750M$=B$(I),"CAT 0")1140*B$(I),"FONT 0")18504B$(I),"FOTO 0")1930>B$(I),"PASTXT 0")2070H540R\A$="RUNNING "+B$(I),16,B)f"79C";A$;:=0pB$(I),16,B) z::SEG=1".D1/SEG.T"t=+B$(I),16,B) yCT=CT+1I=1:I=2I>2=-1:I=I-2:IBOTM<30THPOS=44I=IBOTM/2)*2:=+IBOTM/2)-1:0=+IBOTM/2-.5):I=IBOTM:I/2=I/2)I=I-1 œ2120B=B$(I),16)," ")-1 B$(I),"BASIC 0")850B$(I),"TEXT 0")890 81+LCA):::: RebootN=THPOS:B$(I);XA<8A>11540bA-7640,660,690,720l:=THPOS:B$(I);v:520: 500THPOS=4:I/2=I/2)I=I-1I=IBOTM THPOS=44:I/2<>I/2)I=I+1I13000Zha$="{,|,~,}; selects; to new disk; J/2)=4:=+1:ۙ=44B$(J);:J=J+1I:1,180,22:2,280,21:2,2380,23:8A$(1000),B$(1000),C%(511),C$(20),name$(20):=10:=0UCA=128:LCA=UCA+32CT=15 IF PREFIX$= PREFIX$+MID$(B$(I),1OLUME NAME (/DISKNAME) OR DEVICE NAME (.Dx)"P12);::"80C";a$;:Zb$="CHANGING DISKS"$d=23:=0::"80C";b$;::12).n=12:=20:"MAKE A NEW MENU FOR DISK: ";N$xN$)<2110=N$ :210 I=1L(A$(I),A$))200B$(/ WAP /// SIG MENU.MAKER PROGRAM (v. 6.1) =".D1"210: Coldstart (320: Warmstart &*X=11000: TEXT SLOW-DOWN LOOP ,X.1 CHANGE DISK SUBROUTINE23œ202:2200<RFa$=" YOU MAY SELECT YOUR DISK BY Vhe basis for an access method. In fact, Apple ///'s Record Processing Services package uses a modified version of this algorithm. Hopefully, we'll get the chance to get into those techniques as well. Until then, don't get "out of sorts"! techniques as w"JUNE":1750M$="JULY":1750M$="AUGUST":1750M$="SEPTEMBER":1750M$="OCTOBER":1750M$="NOVEMBER":1750M$="DECEMBER":1750826);"-";M$;" ";Ѡ,2));", ";"19";Р,2);" ";/П,2))=>13П,2))-12;џ,6);:1780$П,2))=0"12";џ,6);:ٟ;$П,2))=>12" PM-":" AM-" 1830WW=1530 =26:=21 1600 &:WW=1:0 :SEG=1;".D1/S EG.F" SEG=1".D1/SEG.G"diskname$=3802  CATCH PASCAL TEXT FILES R",220(204::"79A";""; 2D=1:F=1 <#4;a$ FD=D+1 P#5;a$ZD=60#5;12)dD=60D=1nF=F+1::d$;::Y=1100:Y x13402  CATCH PASCAL TEXT FILES 202 :F*=08:"78C";"SORRY BUT MENU.MAKER CAN'T R".D1/MENU.MAKER",220 d$="" A$="PRINTING "+B$(I),16,B)=01:=0::"80C";A$;:#3,B$(I),16,B)Z=1#3;b$:"78A";b$Z=Z+1:Z=18:1290 1260 #4,B$(I),16,B)#5,".PRINTER"+ž#4#5;12):::".D1/MENU.MAKE30C$="N"C$="n"1160;:=23:=0::"79C";"PRESS ANY KEY TO HALT LISTING": $1020.202 8::Z=1B::=23:=0::"79C";"WOULD YOU LIKE A PRINTED COPY?":1C$:C$<>"Y"C$<>"y"C$<>"N"C$<>"n"1170*C$="N"C$="n"The Third Basic by Taylor Pohlman 79C";"PRESS ANY KEY TO HALT LISTING"::202 1020#2,B$(I),16,B)ž#242:::1160Z=1#2;A$:"78A";A$Z=Z+1:Z>1842:::Z=1980*:=23:=0::"79C";"CONTINUE...?":1C$:C$<>"Y"C$<>"y"C$<>"N"C$<>"n"10 MENU.MAKER TEXT MODULESEG=0"MENU.MAKER"890&*X=11000: TEXT SLOW-DOWN LOOP ,X.1,180,22:2,280,21:2,2380,23:z:A$="LISTING "+B$(I),16,B)$=01:=0::"80C";A$;::12)>=23:=0::"__ _L_i_n_e_____7_7___s_c_a_n_s___t_h_r_o_u_g_h___t_h_e___s_t_r_i_n_g_,___a_n_d___p_r_i_n_t_s___o_u_t___t_h_e___H_E_X___c_o_d_e_s___o_f___e_a_c_h _c_h_a_r_a_c_t_e_r___t_h_lue)? Answer next time! l_l___g_e_t___a_n_y___c_h_a_r_a_c_t_e_r_s___c_u_r_r_e_n_t_l_y___i_n___t_h_e___b_u_f_f_e_r___p_l_a_c_e_d _i_n___t_h_e___v_a_r_i_a_b_l_e___"_c_h_a_r_$_"_.__12405l=ơ):: Routine to back up one directory level.a$=С,l-1) s=a$)a$=a$,s-1)a$,1)="/"5060:s=s-1 5030=a$240( MENU.MAKER 6.10 * Thanks to C.M.Davidson for his help!NOT FOUND.)"X=11000:X:::210Z a$="{,|,~,}; selects; back 1 level; G$:::320H: Error Routine 202:U=11:"79C";"BAD PATH ERROR (NO DISK IN DISK DRIVE OR DESIRED FILE EAD PASCAL TEXT FILES."04=10:"78C";"ANY KEY RETURNS TO THE MENU."!>G$:::".D1/MENU.MAKER",320s___u_s___a___w_a_y___o_u_t___o_f___t_h_e___p_r_o_g_r_a_m_,___b_y___t_e_s_t_i_n_g___f_o_r___a___c_e_r_t_a_i_n___t_w_o_-_b_y_t_e _c_o_m_b_i_n_a_t_i_o_n_._____C_h_e_c_k_i_n_T_U_R_N___i_s___r_e_a_d___a_s___"_0_D___4_1_"___w_h_i_c_h___m_e_a_n_s___c_a_r_r_i_a_g_e___r_e_t_u_r_n___w_i_t_h___"_k_e_y_b_o_a_r_d _o_n_"___a_n_d___"_a_n_y___k_e_y_"___f_e_, _C_l_o_s_e_d_-_A_p_p_l_e_,___C_o_n_t_r_o_l_,___S_h_i_f_t_,___A_l_p_h_a___L_o_c_k___A_)_,___t_h_e_n___c_o_n_s_i_d_e_r___t_h_e___m_o_r_e___u_s_e_f_u_l _o_n_e_s_._____R_E__n_d_l_e_s_s_.____ _W_h_e_n___y_o_u___a_r_e___t_i_r_e_d___o_f___t_r_y_i_n_g___o_u_t___a_l_l___t_h_e___w_e_i_r_d___c_o_m_b_i_n_a_t_i_o_n_s___(_l_i_k_e___O_p_e_n_-_A_p_p_l__A_p_p_l_e___/_/_/___o_n_l_y___h_a_s _t_w_o___f_u_n_c_t_i_o_n___k_e_y_s_,___t_h_i_n_k___a_g_a_i_n_!_____T_h_e___c_o_m_b_i_n_a_t_i_o_n_s___a_r_e___p_r_a_c_t_i_c_a_l_l_y___e_a_r_i_a_t_i_o_n_s___o_n___t_h_e___l_e_t_t_e_r___"_a_"_, _a_n_d___t_h_e_r_e___a_r_e___b_o_u_n_d___t_o___b_e___m_o_r_e_._____F_o_r___t_h_o_s_e___w_h_o___t_h_i_n_k___t_h_e___l_e___j_e_w_e_l_,___y_o_u___c_a_n___t_r_y___s_o_m_e___i_n_t_e_r_e_s_t_i_n_g _t_h_i_n_g_s_._____S_o___f_a_r___I_'_v_e___d_i_s_c_o_v_e_r_e_d___t_e_n___d_i_f_f_e_r_e_n_t___v$_)_d_e_v_i_c_e_$ ___1_1_5___P_R_I_N_T_:_P_R_I_N_T ___1_2_0___C_L_O_S_E_:_I_N_V_O_K_E ___1_2_5___E_N_D _N_o_w___t_h_a_t___y_o_u_'_v_e___t_y_p_e_d___i_n___t_h_i_s___l_i_t_t_u_r_n___t_o___r_e_a_l_i_t_y ___1_0_5___P_E_R_F_O_R_M___c_o_n_t_r_o_l_(_%_3_,_@_h_e_x_0_0_$_)_d_e_v_i_c_e_$ ___1_1_0___P_E_R_F_O_R_M___c_o_n_t_r_o_l_(_%_1_0_,_@_h_e_x_0_0_r_o_u_g_h___1_2_5___c_l_e_a_n___t_h_i_n_g_s___u_p _a_n_d___t_e_r_m_i_n_a_t_e___t_h_e___p_r_o_g_r_a_m_._____T_h_e_y___l_o_o_k___l_i_k_e___t_h_i_s_: ___1_0_0___R_E_M___r_e_to___t_h_e___l_i_t_t_l_e _t_i_m_e_w_a_s_t_e_r___i_n___l_i_n_e___6_0_. _I_n___t_h_e___e_v_e_n_t___o_f___a___m_a_t_c_h___(_o_r___a_n___e_r_r_o_r_)_,___l_i_n_e___1_0_0___t_h_____I_f___t_h_e___c_h_a_r_a_c_t_e_r_s _d_o_n_'_t___m_a_t_c_h_,___t_h_e___r_o_u_t_i_n_e___r_e_s_e_t_s___t_h_e___d_i_s_p_l_a_y___l_o_c_a_t_i_o_n___a_n_d___r_e_t_u_r_n_s___t_i_n_a_t_i_o_n___o_f___k_e_y_s_t_r_o_k_e_s___j_u_s_t___b_y _s_u_b_s_t_i_t_u_t_i_n_g___t_h_e___a_p_p_r_o_p_r_i_a_t_e___c_h_a_r_a_c_t_e_r___c_o_d_e_s___i_n___l_i_n_e___8_0_._l_l_o_w_e_d___b_y___a_n___A_S_C_I_I___1_9_9_)___i_s___a___"_c_o_n_t_r_o_l_-_s_h_i_f_t _T_A_B_"_._____Y_o_u___c_a_n___c_h_a_n_g_e___t_h_e___e_x_i_t___t_o___a_n_y___c_o_m_b_g___y_o_u_r___k_e_y_b_o_a_r_d___c_h_a_r_t___s_h_o_u_l_d___p_r_o_v_e___t_h_a_t___t_h_e___d_e_s_i_r_e_d _c_o_m_b_i_n_a_t_i_o_n___(_r_e_a_d___a_s___a_n___A_S_C_I_I___9___f_o_l_a_g_s___s_e_t_._____E_N_T_E_R_,___o_n___t_h_e___o_t_h_e_r___h_a_n_d_,___i_s___r_e_a_d___a_s___"_0_D___C_1_" _w_h_i_c_h___m_e_a_n_s___c_a_r_r_i_a_g_e___r_e_t_u_r_n___w_i_t_h___"_k_e_y_b_o_a_r_d___o_n_"_,___"_a_n_y___k_e_y_"___a_n_d___"_s_p_e_c_i_a_l___k_e_y_" _f_l_a_g_s___s_e_t_._____S_i_m_i_l_a_r_l_y_,___a___"_1_"___o_n___t_h_e___m_a_i_n_p_r_i_n_t___o_u_t___a___d_i_s_k___f_i_l_e___w_h_i_c_h___w_a_s___p_r_e_v_i_o_u_s_l_y___w_r_i_t_t_e_n___b_y _t_h_e___a_p_p_l_i_c_a_t_i_o_n_._____Y_o_u___c_o_u_l_d___u_s_e___e___w_a_i_t_i_n_g___f_o_r___t_h_e___u_s_e_r___t_o___t_y_p_e___o_n___t_h_e___k_e_y_b_o_a_r_d_._____O_n_e___p_i_e_c_e___o_f _u_s_e_f_u_l___w_o_r_k___w_o_u_l_d___b_e___t_o___a_r_t_i_c_l_e___o_n_e___o_f___t_h_e_s_e___d_a_y_s_,___i_t___t_h_e___a_b_i_l_i_t_y___t_o___d_o___u_s_e_f_u_l___w_o_r_k___w_i_t_h_i_n___a_n _a_p_p_l_i_c_a_t_i_o_n___w_h_i_l_l_y _s_i_m_p_l_i_f_y___t_h_i_n_g_s___f_o_r___t_h_e___u_s_e_r_._____A_n___a_d_d_e_d___b_e_n_e_f_i_t_,___a_n_d___p_o_s_s_i_b_l_y___t_h_e___s_u_b_j_e_c_t___o_f___a _f_u_l_l___l_y___f_r_i_e_n_d_l_y _a_p_p_l_i_c_a_t_i_o_n_s___w_h_i_c_h___u_s_e___t_h_e___k_e_y_p_a_d___a_n_d___o_t_h_e_r___k_e_y_s_t_r_o_k_e___c_o_m_b_i_n_a_t_i_o_n_s___t_o___r_e_a_l___r_e_a_d_s_,___t_h_e___r_e_s_u_l_t___o_f___k_n_o_w_i_n_g _a_l_l___t_h_i_s___s_t_u_f_f___i_s___t_h_e___p_o_s_s_i_b_l_i_t_y___o_f___d_e_v_e_l_o_p_i_n_g___s_o_m_e___r_e_a_l_t_h_a_n___y_o_u___c_o_u_l_d___p_o_s_s_i_b_l_y___w_a_n_t___t_o___k_n_o_w _a_b_o_u_t___a_l_m_o_s_t___e_v_e_r_y_t_h_i_n_g_._____I_n___t_h_e___c_a_s_e___o_f___t_w_o_-_b_y_t_e__r_e_n_t___c_o_m_b_i_n_a_t_i_o_n_s___w_o_u_l_d___b_e___i_n_d_i_s_t_i_n_g_u_i_s_h_a_b_l_e_.____ _A_s___u_s_u_a_l_,___t_h_i_s___c_o_l_u_m_n___t_e_l_l_s___y_o_u___m_o_r_e____m_p_l_e___b_a_c_k_a_r_r_o_w___(_"_0_8___C_1_"_)___f_o_r___c_u_r_s_o_r___m_o_v_e_m_e_n_t_._____W_i_t_h___s_i_n_g_l_e___b_y_t_e___r_e_a_d_s_, _t_h_e_s_e___t_w_o___d_i_f_f_e_A_p_p_l_e_w_r_i_t_e_r___/_/_/___u_s_e_s___"_c_o_n_t_r_o_l_-_b_a_c_k_a_r_r_o_w_"___(_a___"_0_8___C_5_"_)___f_o_r___d_e_l_e_t_i_n_g___a___c_h_a_r_a_c_t_e_r _a_n_d___a___s_is___a_l_l_o_w_s___a _p_r_o_g_r_a_m___t_o___d_i_s_t_i_n_g_u_i_s_h___b_e_t_w_e_e_n___a_n___A_S_C_I_I___b_a_c_k_s_p_a_c_e_,___a_n_d___a___c_u_r_s_o_r___b_a_c_k_s_p_a_c_e_.__ h_e_r___h_a_n_d_y___e_x_a_m_p_l_e _i_s___"_c_o_n_t_r_o_l_-_H_"___i_s___a___"_0_8___4_5_"___w_h_i_l_e___t_h_e___b_a_c_k_a_r_r_o_w___k_e_y___i_s___"_0_8___C_1_"_._____T_h_i_g_r_a_m_s___l_i_k_e___"_W_o_r_d___J_u_g_g_l_e_r_" _c_a_n___u_s_e___t_h_e___n_u_m_e_r_i_c___p_a_d___a_s___a___s_p_e_c_i_a_l___f_u_n_c_t_i_o_n___k_e_y___s_e_t_._____A_n_o_t___k_e_y_b_o_a_r_d___i_s___r_e_a_d___a_s___"_3_1___4_1_"_,___w_h_i_l_e _"_1_"___o_n___t_h_e___n_u_m_e_r_i_c___p_a_d___i_s___"_3_1___C_1_"_._____T_h_a_t_'_s___h_o_w___p_r_o_t_h_e___d_r_i_v_e_r___s_t_a_t_u_s___c_a_l_l_s___w_h_i_c_h___t_e_l_l___h_o_w___m_a_n_y _c_h_a_r_a_c_t_e_r_s___a_r_e___l_e_f_t___t_o___b_e___p_r_i_n_t_e_d___b_y___a___p_r_i_n_t_e_r___d_r_i_v_e_r___a_n_d___o_c_c_a_s_i_o_n_a_l_l_y___p_r_i_n_t _e_n_o_u_g_h___t_o___k_e_e_p___t_h_e___p_r_i_n_t_e_r___b_u_s_y_._____I_n___s_o_m_e___c_i_r_c_l_e_s_,___ Exploring Business Basic Part 12 by Taylor Pohlman In recent months, we've been digging up uses for the various features of the console driver. While last month's four-way scrolling program was valuable, and even fun, there are other, far morARTICLE12v' '*ARTICLE.12!)>6ouse because you're too busy climbing trees. That'll perplex 'em for a while! perplex 'em for a while! ell. Until then, don't get "out of sorts"! ! t_w_o_-_b_y_t_e___r_e_a_d___(_c_o_n_s_i_d_e_r_i_n_g _b_o_t_h___b_y_t_e_s___a_s___o_n_e___1_6___b_i_t___u_n_s_i_g_n_e_d___v_a_l_u_e_)_?_____A_n_s_w_e_r___n_e_x_t___t_i_m_e_U_n_t_i_l___t_h_e_n_,___a___f_i_n_a_l___p_u_z_z_l_e_._____W_h_a_t _c_o_m_b_i_n_a_t_i_o_n___o_f___k_e_y_s___c_r_e_a_t_e_s___t_h_e___l_a_r_g_e_s_t___v_a_l_u_e___f_o_r___a___h_a_t___c_a_n___s_o_r_t___a___t_h_o_u_s_a_n_d___i_t_e_m_s___i_n___a___m_i_n_u_t_e___o_r___s_o_.__ _U_n_b_e_l_i_e_v_e_a_b_l_e_?_______W_a_t_c_h___t_h_i_s___s_p_a_c_e_!_____'_s___c_o_l_u_m_n___i_s___a___l_o_n_g___t_r_e_a_t_i_s_e___o_n___s_o_r_t_i_n_g___t_e_c_h_n_i_q_u_e_s___i_n___B_a_s_i_c_, _i_n_c_l_u_d_i_n_g___s_o_m_e___r_o_u_t_i_n_e_s___t_o_u_t_i_n_e___a_b_o_v_e_,___i_t _b_e_c_o_m_e_s___r_e_l_a_t_i_v_e_l_y___t_r_i_v_a_l_._____G_o_o_d___l_u_c_k_,___a_n_d___h_a_v_e___f_u_n_! _P_._S_._:___N_e_x_t___m_o_n_t_h_t_h_i_s___i_s___k_n_o_w_n___a_s___"_s_p_o_o_l_i_n_g_" _a_n_d___i_s___c_o_n_s_i_d_e_r_e_d___r_e_a_l_l_y___t_r_i_c_k_y_._____W_i_t_h___S_O_S___a_n_d___t_h_e___i_n_p_u_t___r_e typical ways that most applications can use the console features One of the most common of these is the use of data entry screens. Anyone who has programmed has wished for easy ways to generate the data entry screens that are an inevitable pa  The routine below uses initialization from data statements, but, as indicated, a "real" program would use files to contain the screen definitions. 1000 REM initialize tables (could be done from a file) 1005 first.input=0 1007 READ items 101t value only. Next the program performs a gosub to an initialization routine at line 1000. This routine, in addition to filling the three arrays just mentioned, also sets a number of constants and opens the data logging file, if indicated. es are to be displayed, as shown in figure 2. The last array, input.req%, is considerably simpler. It is built during initialization and contains 1 if the field requires input, 0 if no input (titles and so on), and a -1 if the field consists of a defaul the information is contained in arrays in memory. Name$ holds the name of each field to be displayed, along with any default values, in the format shown in figure 1. Info% is an array that contains information about how the field names and valuT"Press any key to quit:"; 620 GET a$ 630 CLOSE 635 END That's a fairly meaty skeleton, but relatively straighforward. First, a word about the three arrays dimensioned in line 5. Since this is a general- purpose data entry routine, allinue:";:GET a$ 500 IF writefile THEN GOSUB 4000 505 NEXT recordnum 600 TEXT:HOME 605 PRINT:PRINT"End of Data Entry for Screen: ";screen$ 610 IF writefile THEN PRINT:PRINT"Output is stored in the file ";outfile$ 615 VPOS=23:HPOS=1:PRINis field goes here 200 GOSUB 3000:REM add to output record string 205 NEXT fieldnum 210 REM code to process the finished record in outrec$ goes here 215 TEXT:HOME:PRINT"Record is: ";out.rec$ 220 PRINT"Press any key to cont escapecode=0:out.rec$="" 110 FOR fieldnum=1 TO items 115 GOSUB 2000:REM process input for field=fieldnum 120 IF escapecode THEN IF fieldnum=first.input THEN TEXT:GOTO 600:ELSE:GOTO 105 125 REM Extra processing for theen$ 35 IF writefile THEN PRINT:PRINT"with output stored in the file ";outfile$ 40 VPOS=23:HPOS=1:PRINT"Press any key to begin:"; 45 GET a$ 100 FOR recordnum=1 TO 32767 105 GOSUB 1500:REM Display the data entry screen with defaults 107 es on the data.) Okay, now that the orientation is over, here's the program skeleton: 1 REM Screen Data Capture Program 5 DIM name$(50,1),info%(50,2),input.req%(50) 20 GOSUB 1000 25 HOME 30 PRINT:PRINT"Data Entry for Screen: ";scrize a screen definition, present the screen, capture the data and store it in a transaction file. In addition to these functions, the routines are designed to allow quite a bit of flexibility in adding features of your own design, especially edit routin program below is generally organized along the following lines: first, a skeleton program that performs a general data entry loop, presenting a screen with fields to be filled in; and second, a series of support subroutines that you can use to initialrt of any busines application. Most programmers sooner or later create or buy software to make that task easier. Not to be outdone, your fearless Basic columnist offers the following tender morsel. (Nope, not so fast; first the sales pitch. The0 FOR i=1 TO items 1015 READ name$(i,0),name$(i,1) 1017 IF MID$(name$(i,0),1,1)=":" THEN input.req%(i)=i:first.input=i 1018 IF MID$(name$(i,0),1,1)="(" THEN input.req%(i)=-1 1020 FOR j=0 TO 2:READ info%(i,j):NEXT j 1025 NEXT i 1030 screen$=name$(0,0):outfile$=name$(0,1) 1035 outlen=info%(0,0) 1040 IF outfile$="" THEN writefile=0:GOTO 1055:ELSE:writefile=1 1045 OPEN#2,outfile$,outlen 1055 set.edit$=CHR$(21)+"0" 1060 set.normal$=CHR$(21)+"1" 1062 REM blank$ belome$(field,0); 1535 GOTO 1600 1550 VPOS=info%(field,0):HPOS=info%(field,1) 1555 PRINT MID$(field$,2,LEN(field$)-1); 1560 IF name$(field,1)="" THEN 1600 1565 PRINT name$(field,1); 1600 NEXT field 1605 RETURN If you it looks like this: 1500 TEXT:HOME 1505 FOR field=1 TO items 1510 field$=name$(field,0) 1515 IF MID$(field$,1,1)=":" THEN 1550 1520 IF MID$(field$,1,1)="(" THEN 1600 1525 VPOS=info%(field,0):HPOS=info%(field,1) 1530 PRINT nadefinition file instead of hard codeing it as we did in this example. In any case, line 100 begins the program's main loop for data entry. The first routine called is the subroutine at line 1500, which displays the screen according to the definitions. lets go back and look at the rest of the program main loop, starting with line 25. Here and through line 45 we create a starter screen, which could certainly be more elaborate if desired. For instance, you could prompt here for the name of the screen each time, but that may need to appear in the output for reference or for meeting another program's requirements. That about wraps up the initialization, leaving us with a set of screen and input definitions for a simple data entry screen. Now as a required lenght of two characters. The last example, on line 1775, is a default field that will appear in all output records. This is a useful option for including fields, such as dates or heading data, that the user should not be required to typefield with no title (the colon is its only definition), it will extend the entire length of the line, a full eighty characters of input space. Line 1765 is an example of a field with a default value, and also one (as indicated in line 1770) that hvalue, and lives on row 3, column 1. Furthermore, it has a maximum allowed length of fifteen characters. Line 1745 is another comment, this this one referring to the field directly under it snd defined on lines 1755 and 1760. Since this is a free-form the general screen definition. Next comes a sample screen comment ("name and address entry") which line 1715 tells us will be positioned on row 1, beginning at column 30. The next field requires input (the leading colon indicates that), has no default rm)","" 1750 DATA 5,1,0 1755 DATA ":","" 1760 DATA 6,1,0 1765 DATA ":State: ","CA" 1770 DATA 8,1,-2 1775 DATA "(","FY1988" 1780 DATA 0,0,0 The definition starts with the number of screen items (both displayable and not) and the next two lines areents as an example: 1700 DATA 7 1705 DATA "My First Screen","" 1707 DATA 117,0,0 1710 DATA "Name and Address Entry","" 1715 DATA 1,30,0 1720 DATA ":First Name: ","" 1730 DATA 3,1,15 1735 DATA ":Last Name: ","" 1740 DATA 3,40,20 1745 DATA "Address (free fo itself is used to determine whether pressing escape should mean stop inputting-orjust start the current screen over. Of more than passing interest is a sample set of screen definitions that this program might process. Consider the following data statem logical expresion to put the index of the first field requiring input in the variable first.input. This could have been done nearly as easily with an IF statement, but there's a speical on logic this week that seemed too good to pass up. First.inputw contains 80 space characters 1065 blank$=" " 1095 RETURN BARGAIN BASEMENT LOGIC Of passing interest in this routine is the use in line 1017 of ahave followed the discussion about field definition, the routine above should prove very straightforward. The next major task of our main program loop occurs at line 110, where an inner loop starts that processes input from each field on the screen, one field at a time. This is accomplished in the subroutine at line 2000, and here's where things get tricky: 2000 field=fieldnum 2002 value$="" 2005 IF input.req%(field)=0 THEN RETURN 2006 IF input.req%(field)<0 THEN value$=name$(field,bd routine wasn't exited in the wrong state. Assuming that there is room in the the window, lines 2220 and 2250 update the pointer and advance the cursor to the new position. Then lines 2255 and 2260 clean up and return to the blink routine, awaiting N FUNNY CHARACTERS After checking for control or special function characters in line 2205, the typed character is inserted into the Line$ string at the current cursor position, and the character is reprinted in inverse to be sure that the on kf to line 2200: 2200 OFF KBD 2205 IF KBD<32 OR KBD>127 THEN 2270 2210 SUB$(line$,point,1)=CHR$(KBD) 2215 INVERSE:PRINT MID$(line$,point,1); 2220 IF point""THEN SUB$(line$,1,LEN (default$))=default$ 2075 INVERSE:HOME 2080 PRINT line$ 2082 PRINT set.edit$; 2085 HPOS=1:point=1tem, by looking ahead at what's next on the screen and adjusting accordingly. Once that is determined, line 2060 establishes the window in which data entry takes place for that item. Next is the printing of any default values and the display (in inverthe screen for each data item that must be input. As you can see, this window definition is relatively easy if the field length is known up front. Lines 2025 through 2045 are designed to determine the actual field length available to a variable-length iTO 2030 2060 WINDOW start.window,row TO end.window,row This routine sets up a field for data entry. Because of the console driver's powerful windowing capabilty, once the size of the field is determined it is possible to construct a cell on (info%(field,2)) 2020 IF field.len<>0 THEN end.window=start.window+field.len-1:GOTO 2060 2021 GOTO 2060 2035 IF info%(test,0)=row THEN end.window=info%(test,1)-1 2036 field.len=end.window-start.window+1:GOTO 2060 2040 test=field+1 2045 GOvey the result of this field's data entry process. Once we determine that actual input must take place, then the real work begins, as shown below: 2008 row=info%(field,0) 2010 start.window=info%(field,1)+LEN(name$(field,0))-1 2015 field.len=ABS1):RETURN ROLL UP YOUR SLEEVES These first few lines are fairly obvious. Using the input.req% array, we can quickly determine if the field is one requiring only default processing or none at all. Note that the string Value$ will be used to conanother keystorke. But what about those special characters? Wait no longer: 2270 IF KBD=27 THEN escapecode=1:POP:RETURN 2275 IF KBD=8 AND point>1 THEN INVERSE:PRINT MID$(line$,point,1);: point=point-1:GOTO 2330 2280 IF KBD=21 AND point141 AND KBD<>9 THEN 2350 2310 valu