Associative Arrays

From AutoIt Wiki
Revision as of 11:02, 28 November 2017 by Skysnake (talk | contribs)
Jump to navigation Jump to search

In computer science, an associative array, map, symbol table, or dictionary is an abstract data type composed of a collection of (key, value) pairs, such that each possible key appears at most once in the collection. An associative array differs from a “normal” array in one major way: rather than being indexed numerically (i.e. 0, 1, 2, 3, …), it is indexed by a key, or a language-like word. A Scripting.Dictionary is a standard object provided by the Microsoft Scripting Runtime (scrrun.dll) dependency and is commonly used in VBScript, a reference to which needs to be added in order to create an object in your AutoIt project.



A few notes on Associative Arrays:

  • Think of an associative array as an array with named elements
  • Comparisons can be exact (binary) or case insensitive (alpha)



At the time of writing this - 28 November 2017 - a new map data type is under development for AutoIt. It is availabe in the current beta release.



A dictionary object example by MHz

; Author Mhz
; Forum https://www.autoitscript.com/forum/topic/182334-scripting-dictionary-modified/
Global $vKey, $sItem, $sMsg, $oDictionary 

; Create dictionary object
$oDictionary = ObjCreate("Scripting.Dictionary")

If @error Then
    MsgBox(0, '', 'Error creating the dictionary object')
Else
    ; Add keys with items
    $oDictionary.Add ("One",    "Same"  )
    $oDictionary.Add ("Two",    "Car"   )
    $oDictionary.Add ("Three",  "House" )
    $oDictionary.Add ("Four",   "Boat"  )

    If $oDictionary.Exists('One') Then
        ; Display item
        MsgBox(0x0, 'Item One', $oDictionary.Item('One'), 2)
        ; Set an item
        $oDictionary.Item('One') = 'Changed'
        ; Display item
        MsgBox(0x20, 'Did Item One Change?', $oDictionary.Item('One'), 3)
        ; Remove key
        $oDictionary.Remove('One')
        ; 
        $oDictionary.Key ('Two') = 'Bike'
    EndIf

    Sleep(1000)

    ; Store items into a variable
    For $vKey In $oDictionary
       $sItem &= $oDictionary.Item($vKey) & @CRLF
    Next

    ; Display items
    MsgBox(0x0, 'Items Count: ' & $oDictionary.Count, $sItem, 3)

    ; Add items into an array
    $aItems = $oDictionary.Items

    ; Display items in the array
    For $i = 0 To $oDictionary.Count -1
        MsgBox(0x0, 'Items [ ' & $i & ' ]', $aItems[$i], 2)
    Next

    Sleep(1000)

    ; Add keys into an array
    $aKeys = $oDictionary.Keys

    ; Display keys in the array
    For $i = 0 To $oDictionary.Count -1
        MsgBox(0x0, 'Keys [ ' & $i & ' ]', $aKeys[$i], 2)
    Next
EndIf



The original AutoIt Scripting Dictionary implementation appears to have been by Gary Frost as far back as 2007. It has since been modified to the following:

#cs
	Scripting Dictionary UDF

	Made by GaryFrost <https://www.autoitscript.com/forum/topic/47048-scripting-dictionary/>
	Modified by Jefrey <jefrey[at]jefrey.ml>
	
	Forum https://www.autoitscript.com/forum/topic/182334-scripting-dictionary-modified/
	
#ce

Func _InitDictionary()
	Return ObjCreate("Scripting.Dictionary")
EndFunc   ;==>_InitDictionary

; Adds a key and item pair to a Dictionary object.
Func _AddItem($oDictionary, $v_key, $v_item)
	$oDictionary.ADD($v_key, $v_item)
	If @error Then Return SetError(1, 1, -1)
EndFunc   ;==>_AddItem

; Returns true if a specified key exists in the Dictionary object, false if it does not.
Func _ItemExists($oDictionary, $v_key)
	Return $oDictionary.Exists($v_key)
EndFunc   ;==>_ItemExists

; Returns an item for a specified key in a Dictionary object
Func _Item($oDictionary, $v_key)
	Return $oDictionary.Item($v_key)
EndFunc   ;==>_Item

; Sets an item for a specified key in a Dictionary object
Func _ChangeItem($oDictionary, $v_key, $v_item)
	$oDictionary.Item($v_key) = $v_item
EndFunc   ;==>_ChangeItem

; Sets a key in a Dictionary object.
Func _ChangeKey($oDictionary, $v_key, $v_newKey)
	$oDictionary.Key($v_key) = $v_newKey
EndFunc   ;==>_ChangeKey

; Removes a key, item pair from a Dictionary object.
Func _ItemRemove($oDictionary, $v_key)
	$oDictionary.Remove($v_key)
	If @error Then Return SetError(1, 1, -1)
EndFunc   ;==>_ItemRemove

; Returns the number of items in a collection or Dictionary object.
Func _ItemCount($oDictionary)
	Return $oDictionary.Count
EndFunc   ;==>_ItemCount

; Returns an array containing all the items in a Dictionary object
Func _GetItems($oDictionary)
	Return $oDictionary.Items
EndFunc   ;==>_GetItems

Func _Clear($oDictionary)
	Return $oDictionary.RemoveAll
EndFunc



Below are several examples that illustrate the usage of the Scripting Dictionary UDF in AutoIt.
Example 1:

; Author Jefrey
; Forum https://www.autoitscript.com/forum/profile/75137-jefrey/
#include 'scriptingdic.au3'
#include <Array.au3> ; Needed only for _ArrayDisplay, and not required by the lib

; Init the object
$oObj = _InitDictionary()

; Adding a single value
_AddItem($oObj, 0, "Test")
; And showing it
msgbox(0, '', _Item($oObj, 0))

; Adding an array
Dim $aArr[] = [0, -80, -49, -44, 80, 100, 8, 7, 6, 5, 4, 3, 2, 1]
_AddItem($oObj, 1, $aArr)
; And showing it
_ArrayDisplay(_Item($oObj, 1))

; We can also use this approach:
$oObj3 = _InitDictionary()
$oObj.Add("test", "foo")
MsgBox(0, '', $oObj.Item("test"))

Example 2 is a reply to a common question asked on the Forum, How to find duplicate lines/values...

; Author Malkey 
;        https://www.autoitscript.com/forum/profile/31256-malkey/
; Forum  https://www.autoitscript.com/forum/topic/116156-best-way-to-compare-strings-for-duplicates/?do=findComment&comment=810752
Local $sStr, $begin, $dif, $sMatches

; Create random test data
For $i = 1 To 1000
    $sStr &= Chr(Random(Asc("a"), Asc("z"), 1)) & Chr(Random(Asc("a"), Asc("z"), 1)) & _
            Chr(Random(Asc("a"), Asc("z"), 1)) & Chr(Random(Asc("a"), Asc("z"), 1)) & @CRLF ;
Next
;ConsoleWrite($sStr & @CRLF)

$begin = TimerInit()

Local $oDict = ObjCreate("Scripting.Dictionary") ; to create a dictionary object
Local $aArray = StringSplit(StringStripWS($sStr, 2), @CRLF, 3)
For $i = 0 To UBound($aArray) - 1
    If $oDict.Exists($aArray[$i]) Then
        $sMatches &= $i & " " & $aArray[$i] & @CRLF ; Record duplicates
    Else
        $oDict.add($aArray[$i], '') ; to store a string (you may set $value = '' if you have no use of it)
    EndIf
Next
$dif = Round(TimerDiff($begin) / 1000, 3) & " secs"

;Display results
If $sMatches <> "" Then
    MsgBox(0, "Results", StringTrimRight($sMatches, 2) & @CRLF & "Time taken = " & $dif)
Else
    MsgBox(0, "Results", "No duplicates found" & @CRLF & "Time taken = " & $dif)
EndIf




_____________________________________________________

The next example does not use the scripting dictionary UDF, but makes use of Global Enumerators. This allows access to data through named elements.

; Author   guinness
; Forum    https://www.autoitscript.com/forum/topic/173803-oop-like-approach-in-autoit/
#include <MsgBoxConstants.au3>

; ---- Start of Person Class

; Stored in the 'object' to verify it's our 'object' and not some random array
Global Const $PERSON_GUID = '4197B285-6AB1-489B-8585-08C852E33F3D'

; Friendly names for 0, 1, 2 and 3
Global Enum $PERSON_AGE, $PERSON_NAME, $PERSON_ID, $PERSON_MAX

; Constructor
Func Person($sName, $iAge)
    Local $hPerson[$PERSON_MAX]

    ; Set the GUID, so as the verification will work
    $hPerson[$PERSON_ID] = $PERSON_GUID

    Person_SetAge($hPerson, $iAge)
    Person_SetName($hPerson, $sName)

    ; Return the Person 'object'
    Return $hPerson
EndFunc   ;==>Person

; Getter for the age property
Func Person_GetAge(ByRef $hPerson)
    Return _Person_IsObject($hPerson) ? $hPerson[$PERSON_AGE] : Null
EndFunc   ;==>Person_GetAge

; Setter for the age property
Func Person_SetAge(ByRef $hPerson, $iAge)
    ; If not a valid 'object' or integer then return
    If Not _Person_IsObject($hPerson) Or Not IsInt($iAge) Then Return

    ; Set the age
    $hPerson[$PERSON_AGE] = $iAge
EndFunc   ;==>Person_SetAge

; Getter for the name property
Func Person_GetName(ByRef $hPerson)
    Return _Person_IsObject($hPerson) ? $hPerson[$PERSON_NAME] : Null
EndFunc   ;==>Person_GetName

; Setter for the name property
Func Person_SetName(ByRef $hPerson, $sName)
    ; If not a valid 'object' then return
    If Not _Person_IsObject($hPerson) Then Return

    ; Set the name
    $hPerson[$PERSON_NAME] = $sName
EndFunc   ;==>Person_SetName

; ToString() for the 'object'
Func Person_ToString(ByRef $hPerson)
    Return _Person_IsObject($hPerson) ? StringFormat('Name: %s, Age: %i', $hPerson[$PERSON_NAME], $hPerson[$PERSON_AGE]) : Null
EndFunc   ;==>Person_ToString

; Check if it's a valid 'object' and not some random array. "NTERNAL ONLY!
Func _Person_IsObject(ByRef $hPerson)
    Return UBound($hPerson) = $PERSON_MAX And $hPerson[$PERSON_ID] == $PERSON_GUID
EndFunc   ;==>_Person_IsObject

; ---- End of Person Class

Example()

Func Example()
    ; Store the Person 'object', which is just a glorified array
    Local $hP1 = Person('John', 30)

    ; Display the 'object'
    MsgBox($MB_SYSTEMMODAL, 'Person 1', Person_ToString($hP1))

    ; Create a new person

    ; Store the Person 'object', which is just a glorified array
    Local $hP2 = Person('James', 36)

    ; Display the 'object'
    MsgBox($MB_SYSTEMMODAL, 'Person 2', Person_ToString($hP2))

    ; Set the age for Person 2
    Person_SetAge($hP2, 45)

    ; Display the 'object'
    MsgBox($MB_SYSTEMMODAL, 'Person 2 - Revised', Person_ToString($hP2))
EndFunc   ;==>Example

Please see the current beta for more information on map functions.