OPT MODULE OPT REG=5, PREPROCESS MODULE 'oomodules/object' -> #define NO_SAFE_STACK 1 -> Uncomment if you don't want to watch the stack. #define PIVOT(l,u) ((l)+(Div(((u)-(l)),(2)))) /* * Exceptions. * * ASAR_EXCEPTION identifies this module as the origin of the exception. The * remaining constants identify the reason for an exception raised by this * module, eg: * * Throw(ASAR_EXCEPTION, ASAR_KEYNOTFOUND) */ EXPORT CONST ASAR_EXCEPTION="AsAr" EXPORT CONST ASAR_KEYNOTFOUND="key", ASAR_STACKOVERFLOW="stak" CONST DEFAULT_LENGTH=64 EXPORT OBJECT associativeArray OF object /****** object/associativeArray ****************************** NAME associativeArray of object -- Dynamic, one-dimensional, ordered array PURPOSE Dynamic, one-dimensional, ordered array for storing things whose indices fit any of these criteria: 1) are non-numeric (most popular index type is a string), 2) are not consecutive and/or have big gaps between values (commonly known as sparse arrays), 3) order cannot be simply determined by builtin E arithmetic operators (=<>). Basically any index type that is not the typical positive integer from 0 to MAXLONG. ATTRIBUTES len:LONG -- current max length of the array tail:LONG -- first empty place after the last element key:PTR TO LONG -- array stores the keys in ordered sequence val:PTR TO LONG -- array stores the values associated with each key in array key EXCEPTIONS ASAR_EXCEPTION identifies this module as the origin of the exception. The remaining constants identify the reason for an exception raised by this module. These are: ASAR_KEYNOTFOUND -- there is no such key in the array ASAR_STACKOVERFLOW -- stack overflow. Should not be raised if the stack watch is enabled. NOTES disposeKey(key) disposeVal(val) These are the default actions for disposal of keys and vals of type LONG, which is "do nothing", since they require no special cleanup. Override them if key and/or val requires cleanup, (ie, dynamically allocated, else your storage WILL NOT be freed when you set() or end()!!!) EXAMPLE: PROC disposeVal(val) OF myAsAr IS DisposeLink(val) Storage for new elements is automatically allocated, increased by 32 each time the array's limits are exceeded. No duplicate keys will ever exist, values are simply overwritten. Inserting and removing from the front of the array is SLOW with large arrays. This can't be helped, however it's often worth the sacrifice for the efficient lookup of binary searches and the handiness of sparse, non-numeric indexed arrays. The binary search function uses recursion and has a FreeStack() check. 4096 should be plenty for most applications since the algorithm is amazingly efficient. Changing any of the PUBLIC (READ-ONLY) values in the object isn't recommended, but hey, who's to stop ya besides the guru? :) SEE ALSO object ********/ len: LONG tail: LONG key: PTR TO LONG val: PTR TO LONG ENDOBJECT /* Local. */ CONST LT=-1, EQ=0, GT=1 /*===========================================================================*/ /*=== Con/Destructors =======================================================*/ /*===========================================================================*/ /* * original new() contents: * self.end() * self.key:=NewR(Mul(length, 4)) * self.val:=NewR(Mul(length, 4)) * self.len:=length * self.tail:=0 * * length is the initial number of empty elements in the array */ PROC init() OF associativeArray /****** associativeArray/init ****************************** NAME init() of associativeArray -- Initialization of the object. SYNOPSIS associativeArray.init() FUNCTION Initializes the object. The list will initially contain DEFAULT_LENGTH elements. EXCEPTION May raise "MEM". SEE ALSO associativeArray ********/ self.key:=NewR(Mul(DEFAULT_LENGTH, 4)) self.val:=NewR(Mul(DEFAULT_LENGTH, 4)) self.len:=DEFAULT_LENGTH self.tail:=0 ENDPROC PROC end() OF associativeArray /****** associativeArray/end ****************************** NAME end() of associativeArray -- Global destructor. SYNOPSIS associativeArray.end() FUNCTION Cleans up keys and values using methods disposeKey() and disposeVal(), so if your keys and values are dynamically allocated, you must override these if you want this method to free them. NOTE: this method was written to be very safe! It may be called directly at any time (even multiple times) to free resources. 'myobj.end()' doesn't free the object, only its contents. Just don't be so silly as to call -any- methods after an 'END myobj'. :) SEE ALSO associativeArray ********/ DEF i, last, ar:PTR TO LONG last:=self.tail-1 IF ar:=self.key FOR i:=0 TO last DO self.disposeKey(ar[i]) Dispose(ar) self.key:=NIL ENDIF IF ar:=self.val FOR i:=0 TO last DO self.disposeVal(ar[i]) Dispose(ar) self.val:=NIL ENDIF self.tail:=0 ENDPROC /*===========================================================================*/ /*=== Tell-me-about-myself Methods ==========================================*/ /*===========================================================================*/ PROC disposeKey(key) OF associativeArray IS EMPTY /****** associativeArray/disposeKey ****************************** NAME disposeKey() of associativeArray -- Call destructor of key. SYNOPSIS associativeArray.disposeKey(LONG) associativeArray.disposeKey(key) FUNCTION Empty method. Special action to take when calling the destructor for an array whose keys are dynamically allocated. Default for type LONG is NO ACTION. INPUTS key:LONG -- Pointer to key. SEE ALSO associativeArray, disposeVal() ********/ PROC disposeVal(val) OF associativeArray IS EMPTY /****** associativeArray/disposeVal ****************************** NAME disposeVal() of associativeArray -- Call destructor of value. SYNOPSIS associativeArray.disposeVal(LONG) associativeArray.disposeVal(val) FUNCTION Empty method. Special action to take when calling the destructor, or overwriting a value for a key that already exists, for an array whose keys are dynamically allocated. Default for type LONG is NO ACTION. INPUTS val:LONG -- Pointer to value. SEE ALSO associativeArray, disposeKey() ********/ PROC testKey(left, right) OF associativeArray IS IF (right>left) THEN 1 ELSE (right0 pos:=binarySearch(self, 0, self.tail-1, key, PIVOT(0,self.tail-1)) rel:=self.testKey(self.key[pos], key) ENDIF IF rel=EQ self.disposeVal(self.val[pos]) ELSE IF rel=GT THEN INC pos makeRoom(self, pos) self.tail:=self.tail+1 ENDIF self.key[pos]:=key self.val[pos]:=val ENDPROC PROC get(searchKey) OF associativeArray /****** associativeArray/get ****************************** NAME get() of associativeArray -- SYNOPSIS associativeArray.get(LONG) associativeArray.get(searchKey) FUNCTION Perform binary search for matching key and return its associated value. INPUTS searchKey:LONG -- the associated key used to identify a value RESULT val:LONG -- value associated with key pos:LONG -- the position of the element in the array. EXCEPTIONS Throws ASAR_EXCEPTION, ASAR_KEYNOTFOUND or ASAR_EXCEPTION, ASAR_STACKOVERFLOW SEE ALSO associativeArray ********/ DEF pos pos:=binarySearch(self, 0, self.tail-1, searchKey, PIVOT(0,self.tail-1)) IF self.testKey(self.key[pos], searchKey) THEN Throw(ASAR_EXCEPTION, ASAR_KEYNOTFOUND) ENDPROC self.val[pos],pos PROC remove(searchKey) OF associativeArray /****** associativeArray/remove ****************************** NAME remove() of associativeArray -- SYNOPSIS associativeArray.remove(LONG) associativeArray.remove(searchKey) FUNCTION Remove the key and value from the array and return them. INPUTS searchKey:LONG -- the key of the element to be removed RESULT key:LONG -- the key you passed val:LONG -- value associated with key EXCEPTIONS Throws ASAR_EXCEPTION, ASAR_KEYNOTFOUND or ASAR_EXCEPTION, ASAR_STACKOVERFLOW SEE ALSO associativeArray ********/ DEF pos, last, i, k:PTR TO LONG, v:PTR TO LONG, key, val pos:=binarySearch(self, 0, self.tail-1, searchKey, PIVOT(0,self.tail-1)) IF self.testKey(self.key[pos], searchKey) THEN Throw(ASAR_EXCEPTION, ASAR_KEYNOTFOUND) last:=self.tail-1 k:=self.key v:=self.val key:=k[pos] val:=v[pos] FOR i:=pos TO last k[i]:=k[i+1] v[i]:=v[i+1] ENDFOR k[i]:=0 v[i]:=0 self.tail:=self.tail-1 ENDPROC key,val /*===========================================================================*/ /*=== Private Support Functions =============================================*/ /*===========================================================================*/ PROC binarySearch(ar:PTR TO associativeArray, l, u, key, pivot) /****** /binarySearch ****************************** NAME binarySearch() -- SYNOPSIS binarySearch(PTR TO associativeArray, LONG, LONG, LONG, LONG) binarySearch(ar, l, u, key, pivot) FUNCTION Recursive binary search of array ar.key. Returns pos when ar.key[pos] equals key, or when l=u. INPUTS ar:PTR TO associativeArray -- array to work on l:LONG -- u:LONG -- key:LONG -- pivot:LONG -- RESULT LONG -- index EXCEPTION Throws ASAR_EXCEPTION, ASAR_STACKOVERFLOW ********/ DEF rel #ifndef NO_SAFE_STACK IF FreeStack()<1000 THEN Throw(ASAR_EXCEPTION, ASAR_STACKOVERFLOW) #endif IF l=u THEN RETURN pivot rel:=ar.testKey(ar.key[pivot], key) IF rel=GT IF l=pivot THEN RETURN pivot+1 l:=pivot ELSEIF rel=LT u:=pivot ELSE RETURN pivot ENDIF ENDPROC binarySearch(ar, l, u, key, PIVOT(l,u)) PROC makeRoom(ar:PTR TO associativeArray, pos) HANDLE /****** /makeRoom ****************************** NAME makeRoom() -- Make room for an element. SYNOPSIS makeRoom(PTR TO associativeArray, LONG) makeRoom(ar, pos) FUNCTION Make a blank element at position pos (for an insert operation). Expand the length of the array by 32 elements if necessary. INPUTS ar:PTR TO associativeArray -- array to work on pos:LONG -- position to insert an element ********/ DEF toKey=NIL:PTR TO LONG, toVal=NIL:PTR TO LONG DEF fromKey:PTR TO LONG, fromVal:PTR TO LONG, i, last fromKey:=ar.key fromVal:=ar.val /* Expand array if necessary and copy elements BEFORE pos, setup so that * upper half of array is copied. Else setup so that upper half of array * is shifted right. **/ IF ar.tail=ar.len toKey:=NewR(ar.len+32*4) toVal:=NewR(ar.len+32*4) last:=pos-1 FOR i:=0 TO last toKey[i]:=fromKey[i] toVal[i]:=fromVal[i] ENDFOR ELSE toKey:=fromKey toVal:=fromVal ENDIF /* Shift upper half of array one position to the right. */ INC pos last:=ar.tail FOR i:=last TO pos STEP -1 toKey[i]:=fromKey[i-1] toVal[i]:=fromVal[i-1] ENDFOR /* Cleanup if the array was expanded. */ IF toKey<>fromKey Dispose(fromKey) Dispose(fromVal) ar.key:=toKey ar.val:=toVal ar.len:=ar.len+32 ENDIF EXCEPT /* The only recovery required is if toVal:=NewR(ar.len+32*4) raises "MEM". */ IF toKey THEN Dispose(toKey) ReThrow() ENDPROC PROC asList() OF associativeArray DEF valueList:PTR TO LONG, keyList:PTR TO LONG, index, numberOfItems numberOfItems := self.tail valueList := List(numberOfItems) keyList := List(numberOfItems) IF (valueList AND keyList) FOR index := 0 TO numberOfItems-1 valueList[index] := self.val[index] keyList[index] := self.key[index] -> WriteF('key: \d, value: $\h\n',self.key[index],self.val[index]) -> WriteF('key: \d, value: $\h\n\n',keyList[index],valueList[index]) ENDFOR -> WriteF('\n') SetList(keyList,numberOfItems) SetList(valueList,numberOfItems) ENDIF RETURN keyList,valueList ENDPROC /*EE folds -1 151 34 247 52 250 33 253 44 260 46 263 58 266 28 EE folds*/