;########################################################################################################################################## ;========================================================================================================================================== ; ; 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. ;------------------------------------------------------------------------------------------------------------------------------------------ ; ;##########################################################################################################################################