Associative Array in WinBatch
;##########################################################################################################################################
;==========================================================================================================================================
;
; Associative Array in WinBatch
;
;==========================================================================================================================================
;
; The following set of WinBatch "User Defined Functions" supports the usage
; of WinBatch arrays with particular effectiveness for lookup purposes.
;
; The UDFs provide methods to work with a WinBatch array as an "Associative Array".
;
; There are different names for the same thing:
;    Associative Array
;    Associative Container
;    Map
;    Dictionary
;    HA - Hashed Array
;    DS - Data Saver
;    CAM - Content Adressable Memory
;
; An "Associative Array" contains a collection of unique keys and a collection of values,
; where each key is associated with one value.
;
; The operation of finding the value associated with a key is called a lookup or indexing,
; and this is the most important operation supported by an associative array.
; The relationship between a key and its data value is sometimes called a mapping or binding.
; For example, if the value associated with the key "chris" is 15, we say that our array maps "chris" to 15.
;
;       Key  ==>  Data
;    +---------+-------+
;    ! "chris" !  16   !
;    ! "dan"   !   1   !
;    +---------+-------+
;
; While a regular array maps an integer index to an arbitrary data type such as string or
; other primitive type, or even object, an associative array's keys can be arbitrarily typed.
;
; In this WinBatch implementation the key value is processed and stored as datatype string.
; A WinBatch array can hold values of different datatypes, so the datatype of the data value part may vary.
;
; For history and credits see at bottom of the script.
;
; Detlev Dalitz.20090526.20090530.20090611.20090612.
;
;==========================================================================================================================================
;##########################################################################################################################################



;##########################################################################################################################################
;==========================================================================================================================================
;
; Constants.
;
;==========================================================================================================================================
;
; DS_KEY_MATCH_SENSITIVE = 0
; DS_KEY_MATCH_LOWERCASE = 1
; DS_KEY_MATCH_UPPERCASE = 2
;
;==========================================================================================================================================
;##########################################################################################################################################



;##########################################################################################################################################
;==========================================================================================================================================
;
; User functions.
;
;==========================================================================================================================================
;
;   DS_GetVersion ()
;   DS_Check (arrDS)
;   DS_GetInfo (arrDS)
;
;   DS_SetConstants ()
;   DS_DropConstants ()
;
;   DS_Open (intMaxCount)
;   DS_OpenFromArray (arrArray, intRowFirst, intRowLast, intColKey, intColData, intKeyMatch)
;   DS_OpenFromList1 (strListKeyData, strDelimiter, intKeyMatch)
;   DS_OpenFromList2 (strListKey, strListData, strDelimiter, intKeyMatch)
;
;   DS_Put (arrDS, anyKey, anyData)
;   DS_Get (arrDS, anyKey)
;   DS_Update (arrDS, anyKey, anyData)
;   DS_Remove (arrDS, anyKey)
;
;   DS_Close (arrDS)
;
;   DS_GetKeyCount (arrDS)
;
;   DS_ContainsKey (arrDS, anyKey)
;   DS_ContainsData (arrDS, anyData)
;
;   DS_GetKeyList (arrDS, strDelimiter)
;   DS_GetDataList (arrDS, strDelimiter)
;   DS_GetKeyDataList (arrDS, strDelimiter)
;   DS_GetKeyListFromData (arrDS, anyData, strDelimiter)
;
;   DS_GetKeyArray (arrDS)
;   DS_GetDataArray (arrDS)
;   DS_GetKeyDataArray (arrDS)
;   DS_GetKeyArrayFromData (arrDS, anyData)
;
;   DS_ArrayFilePutCSV (strFilename, arrDS, strDelimiter, intMode)
;   DS_ArrayFileGetCSV (strFilename, strDelimiter, intMode, intRowFirst, intRowLast, intColKey, intColData, intKeyMatch)
;
;   DS_SetOptionNoKey (arrDS, strNoKey)
;   DS_GetOptionNoKey (arrDS)
;
;   DS_SetOptionKeyMatch (arrDS, intKeyMatch)
;   DS_GetOptionKeyMatch (arrDS)
;
;   DS_SetOptionLoadFactor (arrDS, fltLoadFactor)
;   DS_GetOptionLoadFactor (arrDS)
;   DS_GetCurrentLoadFactor (arrDS)
;
;------------------------------------------------------------------------------------------------------------------------------------------


;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_GetVersion ()
strVersion = Version () ; Version of the WIL EXE program that is currently running.
Terminate (StrLen (strVersion) != 5, "Terminated", IntControl (1004, 0, 0, 0, 0) : @LF : @LF : "DS_GetVersion: WinBatch version is way too old.")
Terminate (StrUpper (strVersion) < "2006D", "Terminated", IntControl (1004, 0, 0, 0, 0) : @LF : @LF : "DS_GetVersion: WinBatch version is too old.")
Return "Data Saver - (c)Detlev Dalitz.20020801.20030218.20090526.20090530.20090611.20090612. - (c)Marty Williams.20020902.20040609."
;..........................................................................................................................................
; This script uses the ':' colon string concatenation operator, so we need ...
; WinBatch version DLL 5.12del. First showing up in WB 2006D.
;
; After replacing the ':' colon string concatenation operator by legacy StrCat () function we need at least ...
; WinBatch version DLL 5.2ceb. First showing up in WB 2004C.
;..........................................................................................................................................
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------

;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_Check (arrDS)
If !ArrInfo (arrDS, -1) Then Return @FALSE ; No array.
If ArrInfo (arrDS, 0) != 2 Then Return @FALSE ; Wrong dimensions.
If ArrInfo (arrDS, 2) != 4 Then Return @FALSE ; Wrong dim2 elements.
If !ArrInfo (arrDS, 6) Then Return @FALSE ; No elements.
If VarType (arrDS [1, 0]) != 1 Then Return @FALSE ; Wrong vartype, must be integer.
If VarType (arrDS [1, 1]) != 1 Then Return @FALSE ; Wrong vartype, must be integer.
If VarType (arrDS [2, 0]) != 65 Then Return @FALSE ; Wrong vartype, must be binary buffer.
If !BinaryBufInfo (arrDS [2, 0], -1) Then Return @FALSE ; No binary buffer associated.
If VarType (arrDS [2, 1]) != 1 Then Return @FALSE ; Wrong vartype, must be integer.
If VarType (arrDS [5, 1]) != 2 Then Return @FALSE ; Wrong vartype, must be string.
If StrTypeInfo (StrTypeInfo (arrDS [5, 1], 0), -1) != "HexDg" Then Return @FALSE ; Wrong StringType, must be hex chars.
Return @TRUE
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------

;------------------------------------------------------------------------------------------------------------------------------------------
#DefineSubRoutine DS_SetConstants ()
DS_KEY_MATCH_SENSITIVE = 0 ; "KeyString".
DS_KEY_MATCH_LOWERCASE = 1 ; "keystring".
DS_KEY_MATCH_UPPERCASE = 2 ; "KEYSTRING".
#EndSubRoutine
;------------------------------------------------------------------------------------------------------------------------------------------

;------------------------------------------------------------------------------------------------------------------------------------------
#DefineSubRoutine DS_DropConstants ()
DropWild ("DS_KEY_MATCH_*")
#EndSubRoutine
;------------------------------------------------------------------------------------------------------------------------------------------

;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_Open (intMaxCount)
Exclusive (@ON)
; Init binary buffer for hash calculation.
intBBSize = 32
hdlBBHash = BinaryAlloc (intBBSize)
BinaryEodSet (hdlBBHash, intBBSize)

; Array size calculation, magic ;-).
intMaxCount = Max (11, intMaxCount) ; If less than 11 elements, then make it 11 elements.
;intSlots = DS_GetPrimeThisOrNext ((2 * intMaxCount) + 1) ; Start with odd number.
intSlots = DS_GetPrimeThisOrNext ((2 << Int (Ceiling (Log10 (intMaxCount) / Log10 (2)))) + 1) ; Start with odd number.

; We need storage for our own values to carry around.
; We reserve some cells from the beginning of the array.
intReservedRows = 7
intD1 = intSlots + intReservedRows
intD2 = 4
arrDS = ArrDimension (intD1, intD2)

; Workaround for missing functionality of Drop(ArrCell) resp. ArrInitialize(ArrCell,Vartype(0))
; We use a column containing boolean values to indicate if this row is unused (0) or used (1).
; Initialize 'Used/Unused' column to unused.
intLow = intReservedRows
intHigh = intD1 - 1
For intI = intLow To intHigh
   arrDS [intI, 2] = 0
Next

; Initialize Reserved Area.
arrDS [0, 0] = intReservedRows ; Number of reserved rows.
arrDS [0, 1] = 0               ; Reserved.
arrDS [0, 2] = 0               ; Reserved.
arrDS [0, 3] = 0               ; Reserved.
arrDS [1, 0] = 0               ; Counter for used slots.
arrDS [1, 1] = intSlots        ; Number of all slots.
arrDS [1, 2] = Int (0.85 * intSlots) ; Maximal number of used slots to be used before throwing "DS hash array is full" error. Default = 85 pct. of all available slots.
arrDS [1, 3] = 0               ; Counter for collisions.
arrDS [2, 0] = hdlBBHash       ; Current handle of the binary buffer used for hash calculation.
arrDS [2, 1] = 1               ; Current key casing. Default=1=lowercase. Note: lowercase gives less collisions than uppercase.
arrDS [2, 2] = 0               ; Reserved.
arrDS [2, 3] = 0               ; Reserved.
arrDS [3, 0] = 0               ; Reserved.
arrDS [3, 1] = 0               ; Reserved.
arrDS [3, 2] = 0               ; Reserved.
arrDS [3, 3] = 0               ; Reserved.
arrDS [4, 0] = "*N/K*"         ; Current "NO KEY" string.
arrDS [4, 1] = "*N/D*"         ; Current "NO DATA" string.
arrDS [4, 2] = 0               ; Reserved.
arrDS [4, 3] = 0               ; Reserved.
arrDS [5, 0] = TimeYmdHms ()   ; Creation DateTime stamp.
arrDS [5, 1] = udfGetGUID ()   ; Brand the array with a unique GUID hexnumber.
arrDS [5, 2] = "Associative Array." ; Descriptive name of the array.
arrDS [5, 3] = "Array contains hashed key-data pairs." ; Description of array content. Miscellaneous informations.
arrDS [6, 0] = 0               ; Reserved.
arrDS [6, 1] = 0               ; Reserved.
arrDS [6, 2] = 0               ; Reserved.
arrDS [6, 3] = "=== End Of Reserved Area ===" ; Reserved.
; The associative array begins right behind the reserved area.

Exclusive (@OFF)
Return arrDS
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------

;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_OpenFromArray (arrArray, intRowFirst, intRowLast, intColKey, intColData, intKeyMatch)
If !ArrInfo (arrArray, -1) Then Return ArrDimension (0) ; No array. Return valid dim0 array with no elements.
If !ArrInfo (arrArray, 6) Then Return ArrDimension (0) ; No elements. Return valid dim0 array with no elements.
If ArrInfo (arrArray, 0) != 2 Then Return ArrDimension (0) ; Wrong dimensions. Return valid dim0 array with no elements.
If intRowFirst >= intRowLast Then Return ArrDimension (0) ; Invalid row area. Return valid dim0 array with no elements.
arrDS = DS_Open (intRowLast - intRowFirst + 1)
DS_SetOptionKeyMatch (arrDS, intKeyMatch)
Switch intColData == -1
Case @TRUE
   For intI = intRowFirst To intRowLast
      If !!VarType (arrArray [intI, intColKey])
         DS_PutGetUpdateRemove (arrDS, 0, arrArray [intI, intColKey], intI)
      EndIf
   Next
   Break
Case @FALSE
   For intI = intRowFirst To intRowLast
      If !!VarType (arrArray [intI, intColKey])
         DS_PutGetUpdateRemove (arrDS, 0, arrArray [intI, intColKey], arrArray [intI, intColData])
      EndIf
   Next
   Break
EndSwitch
Return arrDS
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------

;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_OpenFromList1 (strListKeyData, strDelimiter, intKeyMatch)
If strListKeyData == "" Then Return ArrDimension (0) ; No Key+Data items. Return valid dim0 array with no elements.
strDelim1 = StrSub (strDelimiter, 1, 1)
strDelim2 = StrSub (strDelimiter, 2, 1)
If strDelim2 == "" Then strDelim2 = strDelim1
intItemCount = ItemCount (strListKeyData, strDelim1)
Switch strDelim1 == strDelim2
Case @TRUE ; Itemlist of Key-Data pairs delimited by the same delimiter, e .g. "key-1,data-1,key-2,data-2,...,key-n,data-n".
   arrDS = DS_Open (intItemCount / 2)
   DS_SetOptionKeyMatch (arrDS, intKeyMatch)
   For intI = 1 To intItemCount By 2
      anyKey = ItemExtract (intI, strListKeyData, strDelim1)
      anyData = ItemExtract (intI + 1, strListKeyData, strDelim1)
      DS_PutGetUpdateRemove (arrDS, 0, anyKey, anyData)
   Next
   Break
Case @FALSE ; Itemlist of Key-Data pairs delimited by different delimiters, e .g. "key-1,data-1|key-2,data-2|...|key-n,data-n".
   arrDS = DS_Open (intItemCount)
   DS_SetOptionKeyMatch (arrDS, intKeyMatch)
   For intI = 1 To intItemCount
      strItem = ItemExtract (intI, strListKeyData, strDelim1)
      anyKey = ItemExtract (1, strItem, strDelim2)
      anyData = ItemRemove (1, strItem, strDelim2)
      DS_PutGetUpdateRemove (arrDS, 0, anyKey, anyData)
   Next
   Break
EndSwitch
Return arrDS
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------

;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_OpenFromList2 (strListKey, strListData, strDelimiter, intKeyMatch)
If strListKey == "" Then Return ArrDimension (0) ; No Key items. Return valid dim0 array with no elements.
strDelim1 = StrSub (strDelimiter, 1, 1)
strDelim2 = StrSub (strDelimiter, 2, 1)
intItemCount = ItemCount (strListKey, strDelim1)
arrDS = DS_Open (intItemCount)
DS_SetOptionKeyMatch (arrDS, intKeyMatch)
For intI = 1 To intItemCount
   anyKey = ItemExtract (intI, strListKey, strDelim1)
   anyData = ItemExtract (intI, strListData, strDelim2)
   DS_PutGetUpdateRemove (arrDS, 0, anyKey, anyData)
Next
Return arrDS
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------

;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_Put (arrDS, anyKey, anyData)
Return DS_PutGetUpdateRemove (arrDS, 0, anyKey, anyData)
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_Get (arrDS, anyKey)
Return DS_PutGetUpdateRemove (arrDS, 1, anyKey, 0)
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_Update (arrDS, anyKey, anyData)
Return DS_PutGetUpdateRemove (arrDS, 2, anyKey, anyData)
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_Remove (arrDS, anyKey)
Return DS_PutGetUpdateRemove (arrDS, 3, anyKey, 0)
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------

;------------------------------------------------------------------------------------------------------------------------------------------
#DefineSubRoutine DS_Close (arrDS)
; Free the binary buffer and drop the DS array.
arrDS [2, 0] = BinaryFree (arrDS [2, 0]) ; hdlBBHash.
Drop (arrDS)
#EndSubRoutine
;------------------------------------------------------------------------------------------------------------------------------------------

;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_ContainsKey (arrDS, anyKey)
Return DS_PutGetUpdateRemove (arrDS, 1, anyKey, 0) != arrDS [4, 0]
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_ContainsData (arrDS, anyData) ; Data is case sensitive.
intLow = arrDS [0, 0]
intHigh = intLow + arrDS [1, 0] - 1
For IntI = intLow To intHigh
   If arrDS [arrDS [intI, 3], 1] == anyData Then Return @TRUE
Next
Return @FALSE
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------

;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_GetKeyCount (arrDS)
Return arrDS [1, 0]
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------

;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_GetKeyList (arrDS, strDelimiter)
Return DS_UnloadToList (arrDS, strDelimiter, 0)
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_GetDataList (arrDS, strDelimiter)
Return DS_UnloadToList (arrDS, strDelimiter, 1)
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_GetKeyDataList (arrDS, strDelimiter)
Return DS_UnloadToList (arrDS, strDelimiter, 2)
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_GetKeyListFromData (arrDS, anyData, strDelimiter) ; Data is case sensitive.
strDelimiter = StrSub (strDelimiter, 1, 1)
strItemList = ""
intLow = arrDS [0, 0]
intHigh = intLow + arrDS [1, 0] - 1
For IntI = intLow To intHigh
   If arrDS [arrDS [intI, 3], 1] == anyData Then strItemList = strItemList : strDelimiter : arrDS [arrDS [intI, 3], 0]
Next
If strDelimiter != "" Then strItemList = StrSub (strItemList, 2, -1)
Return strItemList
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------

;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_GetKeyArray (arrDS)
Return DS_UnloadToArray (arrDS, 0)
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_GetDataArray (arrDS)
Return DS_UnloadToArray (arrDS, 1)
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_GetKeyDataArray (arrDS)
Return DS_UnloadToArray (arrDS, 2)
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_GetKeyArrayFromData (arrDS, anyData) ; Data is case sensitive.
strDelimiter = Num2Char (1) ; Surrogate char.
Return Arrayize (DS_GetKeyListFromData (arrDS, anyData, strDelimiter), strDelimiter)
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------

;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_ArrayFilePutCSV (strFilename, arrDS, strDelimiter, intMode)
Return ArrayFilePutCSV (strFilename, DS_UnloadToArray (arrDS, 2), strDelimiter, @FALSE, intMode) ; Returns the number of bytes written to the file.
;..........................................................................................................................................
; strFilename : path and file name of the file to create.
;
; strDelimiter:
; Optional, specifies the character used to separate values on each line of the file.
; It can be any single character except a space, null, carriage return, line feed or quotation mark (double quote).
; If omitted, a comma will be used as the delimiter.
;
; intMode=0 : Use CSV file double quote type processing.
; intMode=2 : Do not use CSV file double quote type processing.
;             This should only be used in cases where you are sure the 'delimiter' will be found nowhere in the data.
;
; By default, each line in the file will be terminated with a CR/LF. This can be changed using IntControl 53.
; Return value is the number of bytes written to the file.
;..........................................................................................................................................
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------

;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_ArrayFileGetCSV (strFilename, strDelimiter, intMode, intRowFirst, intRowLast, intColKey, intColData, intKeyMatch)
arrCSV = ArrayFileGetCSV (strFilename, intMode, strDelimiter, 0, 0)
intE1Last = ArrInfo (arrCSV, 1) - 1
intE2Last = ArrInfo (arrCSV, 2) - 1
intRowFirst = Max (0, Min (intRowFirst, intE1Last))
intRowLast = Max (0, Min (intRowLast, intE1Last))
intColKey = Max (0, Min (intColKey, intE2Last))
intColData = Max (0, Min (intColData, intE2Last))
arrDS = DS_OpenFromArray (ArrayFileGetCSV (strFilename, intMode, strDelimiter, 0, 0), intRowFirst, intRowLast, intColKey, intColData, intKeyMatch)
Drop (arrCSV)
Return arrDS
;..........................................................................................................................................
; strDelimiter:
; Optional, specifies the character used to separate values on each line of the file.
; It can be any single character except a space, null, carriage return, line feed or quotation mark (double quote).
; If omitted, a comma will be used as the delimiter.
;
; intMode=1 : Treat leading and trailing whitespace as significant.
;..........................................................................................................................................
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------


;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_SetOptionNoKey (arrDS, strNoKey)
strNoKeyPrev = arrDS [4, 0]
arrDS [4, 0] = strNoKey ; Set.
Return strNoKeyPrev ; Return previous value.
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_GetOptionNoKey (arrDS)
Return arrDS [4, 0] ; Get. Return current value.
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------

;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_SetOptionKeyMatch (arrDS, intKeyMatch)
intKeyMatchPrev = arrDS [2, 1]
arrDS [2, 1] = Min (Max (0, intKeyMatch), 2) ; Set.
Return intKeyMatchPrev ; Return previous value.
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_GetOptionKeyMatch (arrDS)
Return arrDS [2, 1] ; Get. Return current value.
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------

;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_SetOptionLoadFactor (arrDS, fltLoadFactor)
fltLoadFactorPrev = 1.0 * arrDS [1, 2] / Max (1, arrDS [1, 1]) ; Get current load factor.
arrDS [1, 2] = Int (Max (0, Min (arrDS [1, 1], fltLoadFactor * arrDS [1, 1]))) ; Multiply 'all available slots' by given load factor and set 'maximal slots to be used'.
Return fltLoadFactorPrev ; Return previous value.
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_GetOptionLoadFactor (arrDS)
Return 1.0 * arrDS [1, 2] / Max (1, arrDS [1, 1]) ; Divide 'maximal slots to be used' by 'all available slots' and return result as a float number.
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_GetCurrentLoadFactor (arrDS)
Return 1.0 * arrDS [1, 0] / Max (1, arrDS [1, 1]) ; Divide currently 'used slots' by 'all available slots' and return result.
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------

;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_GetInfo (arrDS)
If !DS_Check (arrDS) Then Return ""
strInfo = "Array Creation DateTime" : @TAB : arrDS [5, 0]
strInfo = strInfo : @LF : "Array GUID" : @TAB : arrDS [5, 1]
strInfo = strInfo : @LF : "Array name" : @TAB : arrDS [5, 2]
strInfo = strInfo : @LF : "Array tag" : @TAB : arrDS [5, 3]
strInfo = strInfo : @LF : "Array reserved rows" : @TAB : arrDS [0, 0]
strInfo = strInfo : @LF : "Array HashBinBuf handle" : @TAB : arrDS [2, 0]
strInfo = strInfo : @LF : "Slots all" : @TAB : arrDS [1, 1]
strInfo = strInfo : @LF : "Slots allowed" : @TAB : arrDS [1, 2]
strInfo = strInfo : @LF : "Slots used" : @TAB : arrDS [1, 0]
strInfo = strInfo : @LF : "Load allowed" : @TAB : 1.0 * arrDS [1, 2] / Max (1, arrDS [1, 1])
strInfo = strInfo : @LF : "Load current" : @TAB : 1.0 * arrDS [1, 0] / Max (1, arrDS [1, 1])
strInfo = strInfo : @LF : "Collisions" : @TAB : arrDS [1, 3]
strInfo = strInfo : @LF : "Collision per used slot" : @TAB : 1.0 * arrDS [1, 3] / Max (1, arrDS [1, 0])
strInfo = strInfo : @LF : "Option 'No Key'" : @TAB : arrDS [4, 0]
strInfo = strInfo : @LF : "Option 'No Data'" : @TAB : arrDS [4, 1]
strInfo = strInfo : @LF : "Option 'Key Match'" : @TAB : arrDS [2, 1]
Return strInfo
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------

;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_SetName (arrDS, anyData)
anyDataPrev = arrDS [5, 2]
arrDS [5, 2] = anyData ; Set.
Return anyDataPrev ; Return previous value.
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_GetName (arrDS)
Return arrDS [5, 2] ; Get. Return current value.
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------

;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_SetTag (arrDS, anyData)
anyDataPrev = arrDS [5, 3]
arrDS [5, 3] = anyData ; Set.
Return anyDataPrev ; Return previous value.
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_GetTag (arrDS)
Return arrDS [5, 3] ; Get. Return current value.
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------

; End of "User functions."
;==========================================================================================================================================
;##########################################################################################################################################




;##########################################################################################################################################
;==========================================================================================================================================
;
; Support functions. Not meant to be called directly by the user.
;
;==========================================================================================================================================
;
;   DS_GetCollisions (arrDS)
;   DS_ResetCollisions (arrDS)
;
;   GetSlotsDefined (arrDS)
;   GetSlotsUsed (arrDS)
;
;   DS_IsPrimeNumber (intNumber)
;   DS_GetPrimeThisOrNext (intNumber)
;
;   DS_RemoveIndex (arrDS, intIndex)
;
;   DS_SetHashBuffer (hdlBBHash, intBBSize)
;   DS_GetHash (anyKey, hdlBBHash)
;
;   DS_PutGetUpdateRemove (arrDS, intMode, anyKey, anyData, intKeyMatch)
;
;   DS_UnloadToArray (arrDS, intMode)
;   DS_UnloadToList (arrDS, strDelimiter, intMode)
;
;==========================================================================================================================================

;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_GetCollisions (arrDS)
Return arrDS [1, 3]
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_ResetCollisions (arrDS)
intCollisionsPrev = arrDS [1, 3]
arrDS [1, 3] = 0
Return intCollisionsPrev
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------

;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction GetSlotsDefined (arrDS)
Return arrDS [1, 1]
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction GetSlotsUsed (arrDS)
Return arrDS [1, 0]
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------

;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_IsPrimeNumber (intNumber)
intIsPrime = 1
intLimit = Int (Sqrt (intNumber))
For intI = 3 To intLimit By 2
   intIsPrime = intNumber mod intI
   If !intIsPrime Then Break
Next
Return !!intIsPrime
;..........................................................................................................................................
; Determine if passed number is prime. Given number must be an odd number.
; This is a simple prime search algorithm adapted to this application case.
;..........................................................................................................................................
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_GetPrimeThisOrNext (intNumber)
While !DS_IsPrimeNumber (intNumber)
   intNumber = intNumber + 2
EndWhile
Return intNumber
;..........................................................................................................................................
; Determine if passed number is prime. Given number must be an odd number.
; If so, then return that number, else locate and return next highest prime number.
; This is a simple prime search algorithm adapted to this application case.
;..........................................................................................................................................
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------

;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_RemoveIndex (arrDS, intIndex)
intLow = arrDS [0, 0]
intHigh = intLow + arrDS [1, 0] - 1
For IntI = intLow To intHigh
   If arrDS [intI, 3] == intIndex
      arrDS [intI, 3] = arrDS [intHigh, 3] ; Copy the index value from the last item into this cell and discard last item.
      arrDS [intHigh, 3] = -1 ; Should be better set to uninitialized Vartype=0.
      arrDS [1, 0] = arrDS [1, 0] - 1 ; Diminish 'used slots' counter by one.
      Break
   EndIf
Next
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------

;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_SetHashBuffer (hdlBBHash, intBBSize)
If intBBSize > BinaryBufInfo (hdlBBHash, 0)
   hdlBBHash = BinaryFree (hdlBBHash)
   hdlBBHash = BinaryAlloc (intBBSize)
EndIf
BinaryEodSet (hdlBBHash, intBBSize)
Return hdlBBHash
;..........................................................................................................................................
; Set the size of the binary buffer accordingly to the size of data string to be checksummed.
; If the buffer is getting too small, then the buffer will be enlarged to fit the new data string size.
; Each DS array has it's own buffer, the returned handle is stored into reserved area in the array.
;..........................................................................................................................................
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_GetHash (anyKey, hdlBBHash)
BinaryPokeStr (hdlBBHash, 0, anyKey)
BinaryPokeHex (hdlBBHash, 0, BinaryChecksum (hdlBBHash, 2)) ; 2=CRC32.
intH = BinaryPeek4 (hdlBBHash, 0)
Return intH ^ (intH >> 16) ; Trick. Get a positive number in WinBatch.
;..........................................................................................................................................
; Calculate a CRC32 hash value. This attempt is good for not too much collisions.
;..........................................................................................................................................
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------

;------------------------------------------------------------------------------------------------------------------------------------------
; Most of the real work happens in here.
; My condolences. Marty's? - Yes, this part was originally coded by Marty Williams, now tweaked to this approach.
#DefineFunction DS_PutGetUpdateRemove (arrDS, intMode, anyKey, anyData)
Exclusive (@ON)
Switch arrDS [2, 1] ; Use option KeyMatch.
Case 1
   anyKey = StrLower (anyKey) ; Makes key case insensitive with lowercased characters.
   Break
Case 2
   anyKey = StrUpper (anyKey) ; Makes key case insensitive with uppercased characters.
   Break
Case 0
   ; anyKey = anyKey ; Leave key case as is.
EndSwitch

intElements = arrDS [1, 1] ; Number of all slots.
intOffset = arrDS [0, 0] ; Add reserved area offset, see function DS_Open.
intIndex = intOffset
intNext = 0
intKeyLen = StrLen (anyKey)
If intKeyLen
   arrDS [2, 0] = DS_SetHashBuffer (arrDS [2, 0], intKeyLen)
   intIndex = intOffset + DS_GetHash (anyKey, arrDS [2, 0]) mod intElements
EndIf
Exclusive (@OFF)
While @TRUE
   Switch intMode
   Case 1 ; Get.
      If !arrDS [intIndex, 2] Then Return arrDS [4, 0] ; If cell is unused, then return optional "NO KEY" string.
      If arrDS [intIndex, 0] == anyKey Then Return arrDS [intIndex, 1]
      Break
   Case 0 ; Set.
      If !arrDS [intIndex, 2] ; If cell is unused, then fill in key and data.
         arrDS [intIndex, 0] = anyKey
         arrDS [intIndex, 1] = anyData
         arrDS [arrDS [0, 0] + arrDS [1, 0], 3] = intIndex ; Located cell = array offset + used slots.
         arrDS [1, 0] = arrDS [1, 0] + 1 ; Count used slots.
         If arrDS [1, 0] > arrDS [1, 2] Then ErrorEvent (-1, 7636, "DS hash array has reached allowed load factor =  " : 1.0 * arrDS [1, 0] / Max (1, arrDS [1, 1]) : @CRLF : "DS max slots = " : arrDS [1, 1] : @CRLF : "DS used slots = " : arrDS [1, 0] : @CRLF : "DS created = " : arrDS [5, 0] : @CRLF : "DS GUID = " : arrDS [5, 1]) ; Throw user defined error 7636 alike "1636 Array subscript out of bounds".
         arrDS [intIndex, 2] = 1  ; Set this index to used.
         Return intIndex ; For index for information.
      EndIf
      If arrDS [intIndex, 0] == anyKey
         arrDS [intIndex, 1] = anyData ; Last same key with any new data wins.
         Return intIndex ; Return index for information.
      EndIf
      ; Slot is used by another key, then handle collision.
      Break
   Case 2 ; Update.
      If !arrDS [intIndex, 2] Then Return arrDS [4, 0] ; If cell is unused, then return optional "NO KEY" string.
      If arrDS [intIndex, 0] == anyKey ; If key exist, then update data.
         anyDataPrev = arrDS [intIndex, 1]
         arrDS [intIndex, 1] = anyData
         Return anyDataPrev ; Return previous data.
      EndIf
      Break
   Case 3 ; Remove.
      If !arrDS [intIndex, 2] Then Return arrDS [4, 0] ; If cell is unused, then return optional "NO KEY" string.
      If arrDS [intIndex, 0] == anyKey ; If key exist, then remove existing key-data pair, but return an array containing this key-data pair.
         arrKD = ArrDimension (2)
         arrKD [0] = arrDS [intIndex, 0]
         arrKD [1] = arrDS [intIndex, 1]
         arrDS [intIndex, 0] = "" ; Should be better set to uninitialized Vartype=0.
         arrDS [intIndex, 1] = "" ; Should be better set to uninitialized Vartype=0.
         arrDS [intIndex, 2] = 0  ; Set this index to unused.
         DS_RemoveIndex (arrDS, intIndex)
         Return arrKD ; Return previous data.
      EndIf
      Break
   EndSwitch

   ; Handle collision.
   arrDS [1, 3] = arrDS [1, 3] + 1 ; Count collisions.
   ; Quadratic probing. Scattered by 7 gives 10 pct. less collisions than scattered by 1.
   intNext = intNext + 1
   intIndex = intOffset + ((intIndex - intOffset + (7 * intNext * intNext)) mod intElements)

EndWhile
#EndFunction
;   ClipAppend (intIndex : @TAB : intNext : @TAB : anyKey : @LF) ; Debug.
;------------------------------------------------------------------------------------------------------------------------------------------

;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_UnloadToArray (arrDS, intMode)
If !ArrInfo (arrDS, -1) Then Return "" ; No array.
If ArrInfo (arrDS, 0) != 2 Then Return "" ; Wrong dimensions.
If !ArrInfo (arrDS, 6) Then Return "" ; No elements.

intLow = arrDS [0, 0] ; Skip reserved area, see function DS_Open.
intHigh = intLow + arrDS [1, 0] - 1

Switch intMode
Case 0 ; Key dim1 array.
   arrArray = ArrDimension (arrDS [1, 0])
   For intI = intLow To intHigh
      arrArray [intI - intLow] = arrDS [arrDS [intI, 3], 0]
   Next
   Return arrArray
   Break
Case 1 ; Data dim1 array.
   arrArray = ArrDimension (arrDS [1, 0])
   For intI = intLow To intHigh
      If !!VarType (arrDS [arrDS [intI, 3], 1]) Then arrArray [intI - intLow] = arrDS [arrDS [intI, 3], 1]
   Next
   Return arrArray
   Break
Case 2 ; KeyData dim2 array.
   arrArray = ArrDimension (arrDS [1, 0], 2)
   For intI = intLow To intHigh
      arrArray [intI - intLow, 0] = arrDS [arrDS [intI, 3], 0]
      If !!VarType (arrDS [arrDS [intI, 3], 1]) Then arrArray [intI - intLow, 1] = arrDS [arrDS [intI, 3], 1]
   Next
   Return arrArray
   Break
EndSwitch
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------

;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction DS_UnloadToList (arrDS, strDelimiter, intMode)
If !ArrInfo (arrDS, -1) Then Return "" ; No array.
If ArrInfo (arrDS, 0) != 2 Then Return "" ; Wrong dimensions.
If !ArrInfo (arrDS, 6) Then Return "" ; No elements.

arrD = ArrDimension (6) ; This array will be automatically dropped when leaving this udf.
ArrInitialize (arrD, "")
intLen = Min (2, StrLen (strDelimiter))
For intI = 1 To intLen
   arrD [intI] = StrSub (strDelimiter, intI, 1)
Next

intLow = arrDS [0, 0] ; Skip reserved area, see function DS_Open.
intHigh = intLow + arrDS [1, 0] - 1

Switch intMode
Case 0 ; KeyList.
   strItemList = ""
   For intI = intLow To intHigh
      strItemList = strItemList : arrD [1] : arrDS [arrDS [intI, 3], 0]
   Next
   If arrD [1] != "" Then strItemList = StrSub (strItemList, 2, -1)
   Return strItemList
   Break
Case 1 ; DataList.
   strItemList = ""
   For intI = intLow To intHigh
      strItemList = strItemList : arrD [1]
      If !!VarType (arrDS [arrDS [intI, 3], 1]) Then strItemList = strItemList : arrDS [arrDS [intI, 3], 1]
   Next
   If arrD [1] != "" Then strItemList = StrSub (strItemList, 2, -1)
   Return strItemList
   Break
Case 2 ; KeyDataList.
   strItemList1 = ""
   For intI = intLow To intHigh
      strItemList2 = arrD [2] : arrDS [arrDS [intI, 3], 0] : arrD [2]
      If !!VarType (arrDS [arrDS [intI, 3], 1]) Then strItemList2 = strItemList2 : arrDS [arrDS [intI, 3], 1]
      If arrD [2] != "" Then strItemList2 = StrSub (strItemList2, 2, -1)
      strItemList1 = strItemList1 : arrD [1] : strItemList2
   Next
   If arrD [1] != "" Then strItemList1 = StrSub (strItemList1, 2, -1)
   Return strItemList1
   Break
EndSwitch
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------

;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction udfGetGUID ()
intBBSize = 16
hdlBB = BinaryAlloc (intBBSize)
BinaryEodSet (hdlBB, intBBSize)
DllCall (DirWindows (1) : "OLE32.DLL", long : "CoCreateGuid", lpbinary : hdlBB)
strGUID = BinaryPeekHex (hdlBB, 0, intBBSize)
hdlBB = BinaryFree (hdlBB)
Return strGUID
;..........................................................................................................................................
; Returns a 128-bit GUID as a string of 16 hex values, i. e. 32 ansi chars, e. g. "402380BC5856214AA956C7EE6D4A084A".
;
; Detlev Dalitz.20090416.
;..........................................................................................................................................
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------
; End of "Support functions. Not meant to be called directly by the user."
;==========================================================================================================================================
;##########################################################################################################################################




;##########################################################################################################################################
;
; History and Credits
;
;==========================================================================================================================================
;
; Detlev Dalitz.200208, published postings in WinBatch Forum about creating of hash accessed arrays.
;
; Marty Williams.20020902, published in WinBatch Forum first WinBatch script "Content addressable memory".
;
; Detlev Dalitz.20030218, published first version of the 'xCAM Content Addressable Memory' extender written in C.
; Extender releases:
; 20030215, 20030219, 20030219, 20030219, 20030220, 20030221, 20030222, 20030223, 20030224, 20030225, 20030225,
; 20030226, 20030227, 20030228, 20030228, 20030301, 20030302, 20030302, 20030306, 20030317, 20030318, 20030705,
; 20030730, 20030731, 20031010, 20031015, 20031016, 20031017, 20031028, 20031029, 20031030, 20031031, 20031101.
; Last version : xcam34i.dll, Fileversion 34032, Productversion 10032.
; Note: In the meantime the xCAM extender array sorting functions have been broken by WinBatch's changing
; of the underlying array access methods, but the CAM related array functions are still working.
;
; Detlev Dalitz.20090526, recoding of Marty's WinBatch script, added some support functions.
;
;------------------------------------------------------------------------------------------------------------------------------------------
;
; Excerpt from the section "History and Credits" from the "xCAM Array Extender".
;
;   WinBatch supports data storage by in-memory arrays since WinBach version
;   2001. An array cell can be simply addressed by an integer index and access
;   is rather quick in relation to WinBatch itemlists.
;
;   In August 2002 I started with some experiments using a hash algorithm to
;   access array cells. I posted some code example to WinBatch forum.
;
;   WinBatch tech supporter guru Mr. Marty Williams suggested to use an
;   alternative hashing function (a xor'ed character by character summing
;   method). (Originally I've used the CRC16 calculation provided by the
;   pChecksum function of the SerialExtender.)
;
;   I did some more tests to find out what hash function comes better to reach
;   the goal of fast access while mimimized use of memory storage.
;
;   Very soon Marty comes up with a WinBatch script in WinBatch forum under
;   Topic "Content addressable memory". This was on "Monday, September 02, 2002
;   06:18 PM".
;
;   Those were the historical meaningful leading words of his script:
;
;   ;**  DataSaver by Marty Williams
;   ;**  With the actual hard parts done by Detlev Dalitz
;   ;**  With support from Marty Williams
;   ;**  Using Hash Addressing originally invented by Dr. Norm Peterson
;   ;**
;   ;**  Purpose: Provide keyword addressable storage in an efficient manner
;
;
;   The script was somewhat quickly thrown together, but it works. It needs
;   only small cosmetic refinements.
;
;   In the meantime sometimes I have read in the WinBatch forum, that people
;   have the need for speedy access to data. Marty often suggested to use the
;   'Data Saver' UDF routines buried down in the WinBatch TechDataBase.
;
;   In Feb. 2003 I got a surprise by Alan Kreutzer. He wrote the famous 'xDlgC'
;   extender for me, which contains just only constants for WinBatch dialogs.
;
;   By this I was guided into the magic forest of writing WinBatch extenders.
;   I had some first insights and got step by step basically knowledge of
;   creating a WinBatch extender on my own.
;
;   Using Alan Kreutzer's 'Simplified Extender' script, published in Summer
;   2002 in WinBatch forum, after a few days and nights, I got my first
;   WinBatch extender 'xmtDD Miscellaneous Tools' working.
;
;   This script is my second approach to WinBatch extenders: The 'xCAM Content
;   Addressable Memory' extender. It is based on the standard C script from the
;   WinBatch SDK. Just being a greenhorn in C language each errorfree code line
;   was like an adventure to me. But finally I got it working.
;
;   Diverging from Marty's proposal I got back to my first solution of using a
;   CRC16 related hash function. The CRC16 routine is quick and gives good
;   results on creating unique hash keys, which reduces collisions. An
;   advantage is, that a CRC16 routine  is much more 'resistent' against
;   similar written words (e.g. anagramms).
;
;   I do not know yet for sure, if a CRC32 routine can be used as an
;   alternative, to catch the whole addressable 32bit memory by a cost of O(1).
;   We will see what we can do in this direction.
;
;   Hope it is of use for practical application.
;
;   Detlev Dalitz
;   DD.20030218
;   - With good thoughts to Marty Williams and Alan Kreutzer.
;
;   Copyright:
;
;   I claim the copyright for my parts of the xCAM extender script and the
;   fundamental idea of hashed access to a WinBatch array. Nevertheless Marty
;   Williams is the man at first who put the idea into the useful form.
;
;   Portions of the 'xCAM Content Addressable Memory' extender code have
;   copyright by WilsonWindowWare and by Alan Kreutzer.
;
;   The extender may be distributed freely. There is no warrenty, use at your
;   own risk.
;------------------------------------------------------------------------------------------------------------------------------------------
;
;##########################################################################################################################################