Author Topic: associative arrays / dictionaries in QB64?  (Read 5949 times)

0 Members and 1 Guest are viewing this topic.

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: associative arrays / dictionaries in QB64?
« Reply #15 on: December 20, 2020, 03:03:15 pm »
Well as you know with computer programming, you can build stuff however you like!

What will be great, will be when AI gets smart enough, that we can create our own languages without having to actually program low-level code. Just tell the AI in plain English what you want the syntax to be like, etc., and it will create a compiler for that. And eventually the tools will be smart enough to where they can translate code written in one programming language into some other language (and paradigm!) of your choice. That's coming, but I'm not holding my breath!

Oh yeah, that's here (almost) probably already in colleges and universities, math, sci and research labs, Google, IBM, Microsoft...  one of Fellippe's Episodes talked about some of that!

Offline TempodiBasic

  • Forum Resident
  • Posts: 1792
    • View Profile
Re: associative arrays / dictionaries in QB64?
« Reply #16 on: December 20, 2020, 07:16:37 pm »
Hi MadSciJr

have you asked another way to create a dictionary ...?
Bplus has showed you the UDT  by TYPE...END TYPE and the array of UDT!
I show here a bidimensional array that manages the issue of 2 value: Key string, value number
Code: QB64: [Select]
  1. ' #############################################################################
  2. ' EMULATE AN ASSOCIATIVE ARRAY
  3.  
  4. ' BASED OFF SOME DISCUSSION AT
  5. ' https://www.qb64.org/forum/index.php?topic=1001.15
  6.  
  7. ' Tries to emulate a simple dictionary / associative array in QB64.
  8.  
  9. ' Some limitations
  10. ' 1. Uses a fixed array size
  11. ' 2. Only stores integer values
  12. ' 3. Not very efficient (simply appends new keys to the end of the array,
  13. '    and searches for them in linear order)
  14. ' 4. Requires declaring and passing around 2 arrays
  15. '    (one for keys, one for values), instead of a single variable.
  16.  
  17. ' Questions
  18. ' 1. Instead of a fixed size, can we implement this with variable arrays and REDIM?
  19. ' 2. How can we get it to store mixed data types?
  20. '    We could store the values as string, and have a third array of "type",
  21. '    but would we have to manually cast each retrieved value with VAL?
  22. ' 3. What are some other ways this could be better implemented?
  23.  
  24. ' #############################################################################
  25.  
  26. ' =============================================================================
  27. ' GLOBAL DECLARATIONS a$=string, i%=integer, L&=long, s!=single, d#=double
  28.  
  29. ' boolean constants
  30. CONST FALSE = 0
  31. CONST TRUE = NOT FALSE
  32.  
  33. CONST cMax = 40
  34. CONST dKey = 2, dValue = 1
  35. ' =============================================================================
  36. ' GLOBAL VARIABLES
  37. DIM ProgramPath$
  38. DIM ProgramName$
  39.  
  40. ' =============================================================================
  41. ' INITIALIZE
  42. ProgramName$ = MID$(COMMAND$(0), _INSTRREV(COMMAND$(0), "\") + 1)
  43. ProgramPath$ = LEFT$(COMMAND$(0), _INSTRREV(COMMAND$(0), "\"))
  44.  
  45.  
  46. ' =============================================================================
  47. ' RUN TEST
  48. TestDictionary
  49.  
  50. ' =============================================================================
  51. ' FINISH
  52. SYSTEM ' return control to the operating system
  53. PRINT ProgramName$ + " finished."
  54.  
  55. ' /////////////////////////////////////////////////////////////////////////////
  56.  
  57. SUB TestDictionary ()
  58.     REDIM arrKey(0 TO cMax, dValue TO dKey) AS STRING ' the element 0,1 is the upper limit index
  59.     DIM iLoop AS INTEGER
  60.     DIM bResult AS INTEGER
  61.     DIM sKey AS STRING
  62.     DIM iValue AS INTEGER
  63.     DIM iDefault AS INTEGER
  64.     DIM iIndex AS INTEGER
  65.  
  66.     CLS
  67.  
  68.     PRINT "-------------------------------------------------------------------------------"
  69.     PRINT "Initializing dictionary..."
  70.     InitDictionary arrKey()
  71.     'PRINT "arrKey(0,dValue) = " + arrKey(0,dvalue))
  72.  
  73.     PRINT: PRINT DumpDictionary$(arrKey()): WaitForEnter
  74.  
  75.     PRINT "-------------------------------------------------------------------------------"
  76.     PRINT "Writing some values..."
  77.  
  78.     sKey = "apple": iValue = 3:
  79.     bResult = SaveDictionary%(arrKey(), sKey, iValue)
  80.     PRINT "SaveDictionary%(arrKey(),  " + CHR$(34) + sKey + CHR$(34) + ", " + cstr$(iValue) + ") returns " + IIFSTR$(bResult, "TRUE", "FALSE")
  81.  
  82.     sKey = "africa": iValue = 1:
  83.     bResult = SaveDictionary%(arrKey(), sKey, iValue)
  84.     PRINT "SaveDictionary%(arrKey(), " + CHR$(34) + sKey + CHR$(34) + ", " + cstr$(iValue) + ") returns " + IIFSTR$(bResult, "TRUE", "FALSE")
  85.  
  86.     sKey = "zebra": iValue = 0:
  87.     bResult = SaveDictionary%(arrKey(), sKey, iValue)
  88.     PRINT "SaveDictionary%(arrKey(), " + CHR$(34) + sKey + CHR$(34) + ", " + cstr$(iValue) + ") returns " + IIFSTR$(bResult, "TRUE", "FALSE")
  89.  
  90.     sKey = "xylophone": iValue = 2:
  91.     bResult = SaveDictionary%(arrKey(), sKey, iValue)
  92.     PRINT "SaveDictionary%(arrKey(), " + CHR$(34) + sKey + CHR$(34) + ", " + cstr$(iValue) + ") returns " + IIFSTR$(bResult, "TRUE", "FALSE")
  93.  
  94.     sKey = "zebra": iValue = 4:
  95.     bResult = SaveDictionary%(arrKey(), sKey, iValue)
  96.     PRINT "SaveDictionary%(arrKey(), " + CHR$(34) + sKey + CHR$(34) + ", " + cstr$(iValue) + ") returns " + IIFSTR$(bResult, "TRUE", "FALSE")
  97.  
  98.     PRINT: PRINT DumpDictionary$(arrKey()): WaitForEnter
  99.  
  100.     PRINT "-------------------------------------------------------------------------------"
  101.     PRINT "Deleting some keys..."
  102.  
  103.     sKey = "zebra": bResult = DeleteKey%(arrKey(), sKey)
  104.     PRINT "DeleteKey%(arrKey(), " + CHR$(34) + sKey + CHR$(34) + ") returns " + IIFSTR$(bResult, "TRUE", "FALSE")
  105.  
  106.     sKey = "nonesuch": bResult = DeleteKey%(arrKey(), sKey)
  107.     PRINT "DeleteKey%(arrKey(), " + CHR$(34) + sKey + CHR$(34) + ") returns " + IIFSTR$(bResult, "TRUE", "FALSE")
  108.  
  109.     sKey = "xylophone": bResult = DeleteKey%(arrKey(), sKey)
  110.     PRINT "DeleteKey%(arrKey(), " + CHR$(34) + sKey + CHR$(34) + ") returns " + IIFSTR$(bResult, "TRUE", "FALSE")
  111.  
  112.     PRINT: PRINT DumpDictionary$(arrKey()): WaitForEnter
  113.  
  114.     PRINT "-------------------------------------------------------------------------------"
  115.     PRINT "Look for keys..."
  116.  
  117.     sKey = "africa": iIndex = FoundKey%(arrKey(), sKey)
  118.     PRINT "FoundKey%(arrKey(),  " + CHR$(34) + sKey + CHR$(34) + ") returns " + cstr$(iIndex) + " which evaluates to " + IIFSTR$(iIndex, "TRUE", "FALSE")
  119.  
  120.     sKey = "xenophobe": iIndex = FoundKey%(arrKey(), sKey)
  121.     PRINT "FoundKey%(arrKey(), " + CHR$(34) + sKey + CHR$(34) + ") returns " + cstr$(iIndex) + " which evaluates to " + IIFSTR$(iIndex, "TRUE", "FALSE")
  122.  
  123.     sKey = "APPLE": iIndex = FoundKey%(arrKey(), sKey)
  124.     PRINT "FoundKey%(arrKey(), " + CHR$(34) + sKey + CHR$(34) + ") returns " + cstr$(iIndex) + " which evaluates to " + IIFSTR$(iIndex, "TRUE", "FALSE")
  125.  
  126.     sKey = "zambia": iIndex = FoundKey%(arrKey(), sKey)
  127.     PRINT "FoundKey%(arrKey(), " + CHR$(34) + sKey + CHR$(34) + ") returns " + cstr$(iIndex) + " which evaluates to " + IIFSTR$(iIndex, "TRUE", "FALSE")
  128.  
  129.     PRINT: PRINT DumpDictionary$(arrKey()): WaitForEnter
  130.  
  131.     PRINT "-------------------------------------------------------------------------------"
  132.     PRINT "Retrieving values..."
  133.  
  134.     sKey = "africa": iDefault = -1000
  135.     PRINT "ReadDictionary%(arrKey(), " + CHR$(34) + sKey + CHR$(34) + ", " + cstr$(iDefault) + ") returns: " + cstr$(ReadDictionary%(arrKey(), sKey, iDefault))
  136.  
  137.     sKey = "zebra": iDefault = -3000
  138.     PRINT "ReadDictionary%(arrKey(),  " + CHR$(34) + sKey + CHR$(34) + ", " + cstr$(iDefault) + ") returns: " + cstr$(ReadDictionary%(arrKey(), sKey, iDefault))
  139.  
  140.     sKey = "apple": iDefault = -2000
  141.     PRINT "ReadDictionary%(arrKey(), " + CHR$(34) + sKey + CHR$(34) + ", " + cstr$(iDefault) + ") returns: " + cstr$(ReadDictionary%(arrKey(), sKey, iDefault))
  142.  
  143.     sKey = "nonesuch": iDefault = -4000
  144.     PRINT "ReadDictionary%(arrKey(),  " + CHR$(34) + sKey + CHR$(34) + ", " + cstr$(iDefault) + ") returns: " + cstr$(ReadDictionary%(arrKey(), sKey, iDefault))
  145.  
  146.     sKey = "xylophone": iDefault = -5000
  147.     PRINT "ReadDictionary%(arrKey(),  " + CHR$(34) + sKey + CHR$(34) + ", " + cstr$(iDefault) + ") returns: " + cstr$(ReadDictionary%(arrKey(), sKey, iDefault))
  148.  
  149.     PRINT: PRINT DumpDictionary$(arrKey()): WaitForEnter
  150.  
  151.     PRINT "-------------------------------------------------------------------------------"
  152.     PRINT "This concludes the test of the pseudo-associative array / dictionary."
  153.     PRINT
  154.  
  155.     WaitForEnter
  156. END SUB ' TestDictionary
  157.  
  158. ' /////////////////////////////////////////////////////////////////////////////
  159.  
  160. SUB WaitForEnter
  161.     DIM in$
  162.     INPUT "Press <ENTER> to continue", in$
  163. END SUB ' WaitForEnter
  164.  
  165. ' /////////////////////////////////////////////////////////////////////////////
  166.  
  167. SUB InitDictionary (Arrkey( ,) AS STRING)
  168.     Arrkey(0, dValue) = _TRIM$(STR$(0))
  169. END SUB ' InitDictionary
  170.  
  171. ' /////////////////////////////////////////////////////////////////////////////
  172.  
  173. FUNCTION SaveDictionary% (arrKey() AS STRING, sKey AS STRING, iValue AS INTEGER)
  174.     DIM iLoop AS INTEGER
  175.     iIndex = 0
  176.     FOR iLoop = 1 TO VAL(arrKey(0, dValue)) ' ubound(arrKey)
  177.         IF LCASE$(arrKey(iLoop, dKey)) = LCASE$(sKey) THEN
  178.             iIndex = iLoop
  179.             EXIT FOR
  180.         END IF
  181.     NEXT iLoop
  182.     IF (iIndex > 0) THEN
  183.         ' KEY EXISTS, UPDATE VALUE
  184.         arrKey(iIndex, dValue) = _TRIM$(STR$(iValue))
  185.         SaveDictionary% = TRUE
  186.     ELSE
  187.         ' KEY DOESN'T EXIST, ADD IT
  188.  
  189.         ' IS THERE ROOM?
  190.         iIndex = VAL(arrKey(0, dValue)) + 1
  191.         IF iIndex > UBOUND(arrKey) THEN
  192.             ' DICTIONARY FULL!
  193.             SaveDictionary% = FALSE
  194.         ELSE
  195.             ' ADD VALUE AND KEY
  196.             arrKey(0, dValue) = _TRIM$(STR$(iIndex))
  197.             arrKey(iIndex, dValue) = _TRIM$(STR$(iValue))
  198.             arrKey(iIndex, dKey) = sKey
  199.             SaveDictionary% = TRUE
  200.         END IF
  201.     END IF
  202. END FUNCTION ' SaveDictionary%
  203.  
  204. ' /////////////////////////////////////////////////////////////////////////////
  205.  
  206. FUNCTION FoundKey% (arrKey() AS STRING, sKey AS STRING)
  207.     DIM iLoop AS INTEGER
  208.     DIM iIndex AS INTEGER
  209.  
  210.     iIndex = 0
  211.     FOR iLoop = 1 TO VAL(arrKey(0, dValue)) ' ubound(arrKey)
  212.         IF LCASE$(arrKey(iLoop, dKey)) = LCASE$(sKey) THEN
  213.             iIndex = iLoop
  214.             EXIT FOR
  215.         END IF
  216.     NEXT iLoop
  217.  
  218.     FoundKey% = iIndex
  219. END FUNCTION ' FoundKey%
  220.  
  221. ' /////////////////////////////////////////////////////////////////////////////
  222.  
  223. FUNCTION DeleteKey% (arrKey() AS STRING, sKey AS STRING)
  224.     DIM iLoop AS INTEGER
  225.     DIM iIndex AS INTEGER
  226.     DIM bResult AS INTEGER
  227.  
  228.     iIndex = 0
  229.     FOR iLoop = 1 TO VAL(arrKey(0, dValue)) ' ubound(arrKey)
  230.         IF LCASE$(arrKey(iLoop, dKey)) = LCASE$(sKey) THEN
  231.             iIndex = iLoop
  232.             EXIT FOR
  233.         END IF
  234.     NEXT iLoop
  235.  
  236.     IF (iIndex = 0) THEN
  237.         bResult = FALSE
  238.     ELSE
  239.         IF iIndex < VAL(arrKey(0, dValue)) THEN
  240.             FOR iLoop = iIndex TO (VAL(arrKey(0, dValue)) - 1)
  241.                 arrKey(iLoop, dKey) = arrKey(iLoop + 1, dKey)
  242.                 arrKey(iLoop, dValue) = arrKey(iLoop + 1, dValue)
  243.             NEXT iLoop
  244.         END IF
  245.         arrKey(0, dValue) = _TRIM$(STR$(VAL(arrKey(0, dValue)) - 1))
  246.         bResult = TRUE
  247.     END IF
  248.  
  249.     DeleteKey% = bResult
  250. END FUNCTION ' DeleteKey%
  251.  
  252. ' /////////////////////////////////////////////////////////////////////////////
  253.  
  254. FUNCTION DumpDictionary$ (arrKey() AS STRING)
  255.     DIM iLoop AS INTEGER
  256.     DIM sResult AS STRING
  257.     sResult = ""
  258.     sResult = sResult + "Dictionary size: " + arrKey(0, dValue) + CHR$(13)
  259.     FOR iLoop = 1 TO VAL(arrKey(0, dValue)) ' ubound(arrKey)
  260.         sResult = sResult + "Item(" + CHR$(34) + arrKey(iLoop, dKey) + CHR$(34) + ") = " + (arrKey(iLoop, dValue)) + CHR$(13)
  261.     NEXT iLoop
  262.     DumpDictionary$ = sResult
  263. END FUNCTION ' DumpDictionary$
  264.  
  265. ' /////////////////////////////////////////////////////////////////////////////
  266.  
  267. FUNCTION ReadDictionary% (arrKey() AS STRING, sKey AS STRING, iDefault AS INTEGER)
  268.     DIM iLoop AS INTEGER
  269.     DIM iIndex AS INTEGER
  270.  
  271.     iIndex = 0
  272.     FOR iLoop = 1 TO VAL(arrKey(0, dValue)) ' ubound(arrKey)
  273.         IF LCASE$(arrKey(iLoop, dKey)) = LCASE$(sKey) THEN
  274.             iIndex = iLoop
  275.             EXIT FOR
  276.         END IF
  277.     NEXT iLoop
  278.  
  279.     IF (iIndex = 0) THEN
  280.         ReadDictionary% = iDefault
  281.     ELSE
  282.         ReadDictionary% = VAL(arrKey(iIndex, dValue))
  283.     END IF
  284. END FUNCTION ' ReadDictionary%
  285.  
  286. ' /////////////////////////////////////////////////////////////////////////////
  287.  
  288. FUNCTION cstr$ (myValue)
  289.     cstr = LTRIM$(RTRIM$(STR$(myValue)))
  290. END FUNCTION ' cstr$
  291.  
  292. ' /////////////////////////////////////////////////////////////////////////////
  293.  
  294. FUNCTION IIF (Condition, IfTrue, IfFalse)
  295.     IF Condition THEN IIF = IfTrue ELSE IIF = IfFalse
  296.  
  297. ' /////////////////////////////////////////////////////////////////////////////
  298.  
  299. FUNCTION IIFSTR$ (Condition, IfTrue$, IfFalse$)
  300.     IF Condition THEN IIFSTR$ = IfTrue$ ELSE IIFSTR$ = IfFalse$
  301.  
  302. ' /////////////////////////////////////////////////////////////////////////////
  303.  
  304.  

As you can see, if you run this code, it works and the output is the same except for the iDefault values used in the Retrieving values section. I hate not so clear values so I use the thousands for iDefault (-1000,-3000 ....-4000)!

About your little database:
1. have you built it to overwrite the value if the new key is already in dictionary? Or it is a feature coming out itself?
In other words: have you decided to not have a duplicate of a Key?

2. you use a raw bubblesort to find the key into the database. Bplus have suggested to use the binary algorhytm to improve the searching section.  I want to stress that if you want a speed database you need of as many  indexes as many elements are different in the structure of database. The indexes array as integer must have 2 dimensions, one for Keys and the other for values.

3. why do you use a short main module and the work of main module has been made by a SUB (TestDictionary)? So you cut out any use of SHARED, or DIM SHARED or REDIM SHARED and you need to pass the data as parameters.

« Last Edit: December 20, 2020, 07:19:40 pm by TempodiBasic »
Programming isn't difficult, only it's  consuming time and coffee

Offline luke

  • Administrator
  • Seasoned Forum Regular
  • Posts: 324
    • View Profile
Re: associative arrays / dictionaries in QB64?
« Reply #17 on: December 20, 2020, 08:12:31 pm »
My interpreter uses a hash table for storing program symbols. I've pulled it out and added a small demo program.

It does a proper hashing of the contents and can support any kind of data for the "value" half because it works with a UDT, but it does require two SHARED arrays and a SHARED variable. I suppose if you really wanted to you could convert it to a _MEM based thing with keep it all as local variables, but I only needed one instance of the table in my case.

Code: [Select]
DEFLNG A-Z

'Object to store in the symbol table
TYPE symtab_entry_t
    identifier AS STRING
    v1 AS LONG
    v2 AS LONG
    v3 AS LONG
END TYPE

'Actual stored obejcts
DIM SHARED symtab(1000) AS symtab_entry_t
'Mapping between hash and index
DIM SHARED symtab_map(1750) AS LONG
DIM SHARED symtab_last_entry AS LONG

'The symtab optionally supports transactions; calling symtab_rollback will
'remove all items added since the last call to symtab_commit.
DIM SHARED symtab_last_commit_id



DIM entry AS symtab_entry_t
entry.identifier = "rambunctious"
entry.v1 = 12
entry.v2 = 5
entry.v3 = 7
symtab_add_entry entry

entry.identifier = "contradict"
entry.v1 = 10
entry.v2 = 3
entry.v3 = 7
symtab_add_entry entry

entry.identifier = "explode"
entry.v1 = 7
entry.v2 = 3
entry.v3 = 4
symtab_add_entry entry

PRINT "Table has"; symtab_last_entry; "entries:"
FOR i = 1 TO symtab_last_entry
    PRINT "    "; symtab(i).identifier
NEXT i

DO
    INPUT "Select item:", lookupkey$
    result = symtab_get_id(lookupkey$)
    IF result = 0 THEN
        PRINT lookupkey$; " not in table"
    ELSE
        PRINT lookupkey$; " has"; symtab(result).v1; "letters;"; symtab(result).v2; "vowels and"; symtab(result).v3; "consonants."
    END IF
LOOP



'Copyright 2020 Luke Ceddia
'SPDX-License-Identifier: Apache-2.0
'symtab.bm - Symbol Table

SUB symtab_add_entry (entry AS symtab_entry_t)
    symtab_expand_if_needed
    symtab_last_entry = symtab_last_entry + 1
    symtab(symtab_last_entry) = entry
    symtab_map_insert entry.identifier, symtab_last_entry
END SUB

FUNCTION symtab_get_id (identifier$)
    h~& = symtab_hash~&(identifier$, UBOUND(symtab_map))
    DO
        id = symtab_map(h~&)
        IF id = 0 THEN
            EXIT FUNCTION
        END IF
        IF symtab(id).identifier = identifier$ THEN
            symtab_get_id = id
            EXIT FUNCTION
        END IF
        h~& = (h~& + 1) MOD (UBOUND(symtab_map) + 1)
    LOOP
END FUNCTION

'I'd like to be able to use this code for recoverable errors when in interactive
'mode (rollback if an invalid line was entered), but it's not clear how one does
'recoverable error handling in the parser since we don't have proper exceptions.
SUB symtab_commit
    symtab_last_commit_id = symtab_last_entry
END SUB

SUB symtab_rollback
    'Would it be more efficient to do this in reverse order?
    'Does anyone care about how fast it is?
    FOR i = symtab_last_commit_id + 1 TO symtab_last_entry
        identifier$ = symtab(i).identifier
        h~& = symtab_hash~&(identifier$, UBOUND(symtab_map))
        DO
            id = symtab_map(h~&)
            IF symtab(id).identifier = identifier$ THEN EXIT DO
            h~& = (h~& + 1) MOD (UBOUND(symtab_map) + 1)
        LOOP
        symtab_map(h~&) = 0
    NEXT i
    symtab_last_entry = symtab_last_commit_id
END SUB

'Strictly internal functions below
SUB symtab_expand_if_needed
    CONST SYMTAB_MAX_LOADING = 0.75
    CONST SYMTAB_GROWTH_FACTOR = 2
    IF symtab_last_entry = UBOUND(symtab) THEN
        REDIM _PRESERVE symtab(UBOUND(symtab) * SYMTAB_GROWTH_FACTOR) AS symtab_entry_t
    END IF

    IF symtab_last_entry / UBOUND(symtab_map) <= SYMTAB_MAX_LOADING THEN EXIT FUNCTION
    REDIM symtab_map(UBOUND(symtab_map) * SYMTAB_GROWTH_FACTOR)
    FOR i = 1 TO symtab_last_entry
        symtab_map_insert symtab(i).identifier, i
    NEXT i
END SUB

SUB symtab_map_insert (k$, v)
    h~& = symtab_hash~&(k$, UBOUND(symtab_map))
    DO
        IF symtab_map(h~&) = 0 THEN EXIT DO
        h~& = (h~& + 1) MOD (UBOUND(symtab_map) + 1)
    LOOP
    symtab_map(h~&) = v
END SUB

'http://www.cse.yorku.ca/~oz/hash.html
'Attributed to D. J. Bernstein
FUNCTION symtab_hash~& (k$, max)
    hash~& = 5381
    FOR i = 1 TO LEN(k$)
        hash~& = ((hash~& * 33) XOR ASC(k$, i)) MOD max
    NEXT i
    '0<=hash<=max-1, so 1<=hash+1<=max
    symtab_hash~& = hash~& + 1
END FUNCTION