OHB

EASY Multidimensional Associative Arrays

44 posts in this topic

#1 ·  Posted (edited)

Hi all! I've spent a lot of time reading these forums and I have learned MUCH. So, thanks for that!

Anyway, I decided I wanted multidimensional associative arrays for use in my scripts...and I couldn't find anything I liked. So, I wrote one!

Here's the code:

#include-once
; #INDEX# =======================================================================================================================
; Title .........: xHashCollection
; AutoIt Version : 3.3.4.0
; Language ......: English
; Description ...: Create and use Multidimentional Associative Arrays
; Author ........: OHB <me at orangehairedboy dot com>
; ===============================================================================================================================
Global $_xHashCollection = ObjCreate( "Scripting.Dictionary" ), $_xHashCache

; #FUNCTION# ====================================================================================================================
; Name...........: x
; Description ...: Gets or sets a value in an Associative Array
; Syntax.........: SET: x( $sKey , $vValue )
;                  GET: x( $key )
; Parameters ....: $sKey - the key to set or get. Examples:
;                  x( 'foo' )           gets value of foo
;                  x( 'foo.bar' )       gets value of bar which is a key of foo
;                  $bar = "baz"
;                  x( 'foo.$bar' )      gets value of baz which is a key of foo (variables are expanded)
; Return values .: Success - When setting, return the value set. When getting, returns the requested value.
;                  Failure - Returns a 0
; Author ........: OHB <me at orangehairedboy dot com>
; ===============================================================================================================================
Func x( $sKey = '' , $vValue = '' )
    $func = "get"
    If @NumParams <> 1 Then $func = "set"
    If $sKey == '' Then
        If $func == "get" Then
            Return $_xHashCollection
        Else
            $_xHashCollection.removeAll
            Return ''
        EndIf
    EndIf
    $parts = StringSplit( $sKey , "." )
    $last_key = $parts[$parts[0]]
    $cur = $_xHashCollection
    For $x = 1 To $parts[0] - 1
        If Not $cur.exists( $parts[$x] ) Then
            If $func == "get" Then Return
            $cur.add( $parts[$x] , ObjCreate( "Scripting.Dictionary" ) )
        EndIf
        $cur = $cur.item( $parts[$x] )
    Next
    If IsPtr( $vValue ) Then $vValue = String( $vValue )
    If $func == "get" Then
        If Not $cur.exists( $last_key ) Then Return
        $item = $cur.item( $last_key )
        Return $item
    ElseIf Not $cur.exists( $last_key ) Then
        $cur.add( $last_key , $vValue )
    Else
        $cur.item( $last_key ) = $vValue
    EndIf
    Return $vValue
EndFunc

; #FUNCTION# ====================================================================================================================
; Name...........: x_unset
; Description ...: Removes a key from an Associative Array
; Syntax.........: x_unset( $sKey )
; Parameters ....: $sKey - the key to remove.
; Return values .: Success - True
;                  Failure - False
; Author ........: OHB <me at orangehairedboy dot com>
; ===============================================================================================================================
Func x_unset( $sKey )
    If $sKey == '' Then Return x( '' , '' )
    $parts = StringSplit( $sKey , "." )
    $cur = $_xHashCollection
    For $x = 1 To $parts[0] - 1
        If Not $cur.exists( $parts[$x] ) Then Return False
        $cur = $cur.item( $parts[$x] )
    Next
    $cur.remove( $parts[$parts[0]] )
    Return True
EndFunc

; #FUNCTION# ====================================================================================================================
; Name...........: x_display
; Description ...: Displays the contents of an Associative Array
; Syntax.........: x_display( $sKey )
; Parameters ....: $sKey - the key to display. Examples:
;                  x_display()          displays everything
;                  x_display( 'foo' )   displays the contents of foo
; Author ........: OHB <me at orangehairedboy dot com>
; ===============================================================================================================================
Func x_display( $key = '' )
    $text = $key
    If $key <> '' Then $text &= " "
    $text &= StringTrimRight( _x_display( x( $key ) , '' ) , 2 )
    $wHnd = GUICreate( "Array " & $key , 700 , 500 )
    GUISetState( @SW_SHOW , $wHnd )
    $block = GUICtrlCreateEdit( $text , 5 , 5 , 690 , 490 )
    GUICtrlSetFont( $block , 10 , 400 , -1 , 'Courier' )
    While 1
        If GUIGetMsg() == -3 Then ExitLoop
    WEnd
    GUISetState( @SW_HIDE , $wHnd )
    GUIDelete( $wHnd )
EndFunc

; #INTERNAL_USE_ONLY# ===========================================================================================================
; Name...........: _x_display
; Description ...: Itterates through an array and builds output for x_display
; Author ........: OHB <me at orangehairedboy dot com>
; ===============================================================================================================================
Func _x_display( $item , $tab )
    If IsObj( $item ) Then
        $text = 'Array (' & @CRLF
        $itemAdded = False
        For $i In $item
            $text &= $tab & "  [" & $i & "] => " & _x_display( $item.item($i) , $tab & "  " )
            $itemAdded = True
        Next
        If Not $itemAdded Then $text &= @CRLF
        $text &= $tab & ')'
    ElseIf IsArray( $item ) Then
        $text = "Array"
        $totalItems = 1
        $dimensions = UBound( $item , 0 )
        For $dimension = 1 To $dimensions
            $size = UBound( $item , $dimension )
            $totalItems *= $size
            $text &= "[" & $size & "]"
        Next
        $text &= " (" & @CRLF
        For $itemID = 0 To $totalItems - 1
            $idName = ''
            $idNum = $itemID
            For $dimension = 1 To $dimensions - 1
                $mul = ( $totalItems / UBound( $item , $dimension ) )
                $a = Floor( $idNum / $mul )
                $idName &= '[' & $a & ']'
                $idNum -= ( $a * $mul )
            Next
            $idName &= '[' & $idNum & ']'
            $text &= $tab & "  " & $idName & " => " & _x_display( Execute( "$item" & $idName ) , $tab & "  " )
        Next
        $text &= $tab & ")"
    Else
        $text = $item
    EndIf
    $text &= @CRLF
    Return $text
EndFunc

And here's how to use it:

x( 'foo' , 'bar' ) ; foo = bar
x( 'bar.foo' , 'baz' ) ; bar[foo] = baz
MsgBox( 0 , x( 'foo' ) , x( 'bar.foo' ) ); outputs 'bar' and 'baz'

The x function (chosen so it has a nice short name) takes two parameters: $key, $value. If the value is supplied, the value is set, otherwise it is retrieved.

Create multi-dimensional arrays by using dot-notation.

x( 'family.father.name' , 'Paul' )

is equivalent to the following PHP command:

$family['father']['name'] = 'Paul';

Retrieve the value the same way!

msgbox( 0 , 'Family' , 'My father's name is' & x( 'family.father.name' ) )

And, of course, you can use dynamic names using variables (provided the values don't contain dots).

x( "localization.en.lang_en","English")
x( "localization.es.lang_en","Ingles")
For $language_code In x( 'localization' )
    MsgBox( 0 , 'Language' , x( 'localization.'&$language_code&'.lang_en' ) )
Next

Setting a value returns the set value, so you can do things like:

x( 'foo' , x( 'bar' , 'baz' ) ) ;foo and bar both contain baz
If ( x( 'query' , $query ) == '' ) MsgBox( 0 , 'Error' , 'Query cannot be blank!' )

You can use x_display to have a look at the structure.

x( 'foo' , x( 'bar' , 'baz' ) )
x( 'yada' , 'yada' )
x_display() ;display everything
x_display( 'foo' ) ;display just foo

TODO:

* Serialization / Unserialization

I would appreciate feedback and suggestions!

Enjoy

Edited by OHB
1 person likes this

Share this post


Link to post
Share on other sites



#2 ·  Posted (edited)

I like this very much. Very nice first post, hope you stick around here for a bit. One small error checking suggestion. In your version the code crashes if I do this

x( 'beef' , 'Meat' )
x( 'poultry' , 'Meat' )
x( 'beef' , 'Fruit' )
MsgBox( 0 , x( 'poultry' ) , x( 'beef' ) )

To solve this, you can add this:

if @error then Return

just before this line

$cur.add( $last_key , $value
Edited by picea892

Share this post


Link to post
Share on other sites

#3 ·  Posted (edited)

I like this very much. Very nice first post, hope you stick around here for a bit. One small error checking suggestion. In your version the code crashes if I do this

x( 'beef' , 'Meat' )
x( 'poultry' , 'Meat' )
x( 'beef' , 'Fruit' )
MsgBox( 0 , x( 'poultry' ) , x( 'beef' ) )

Edit: Code corrected in first post. Edited by OHB

Share this post


Link to post
Share on other sites

Not sure what the problem is with the edit button, should be bottom right.

I have reported your post (bottom left) and asked them to move it to the examples forum.

Share this post


Link to post
Share on other sites

Hi again

Not my best work, but am out of time. Here is where I think you could go with this....

#include <GUIConstantsEx.au3>
#include <Array.au3>
#include-once

Global $_xHashCollection = ObjCreate( "Scripting.Dictionary" )
dim $array[10][2]=[["Pine","Conifer"],["Spruce","Conifer"],["Larch","Conifer"],["Fir","Conifer"],["Poplar","Deciduous"],["Birch","Deciduous"],["Maple","Deciduous"],["Oak","Deciduous"],["Ash","Deciduous"],["Basswood","Deciduous"]]

_arraytox($array)

_ArrayDisplay(_xvalue("Conifer"))

func _arraytox($arrayed)
    $rows = UBound($arrayed)
    $cols = UBound($arrayed, 2)
    for $i = 0 to $rows-1
        $ongoing=""
        for $j=0 to $cols-1
            $ongoing=$ongoing&$arrayed[$i][$j]
            if $j<>$cols-1 then $ongoing=$ongoing&","
        Next
            x($ongoing,"x") ;the "x" is just a placeholder to trigger @NumParams in the x function
    Next
EndFunc

func _xvalue($value)
    $sItem=""
    $count=0
    For $vKey In $_xHashCollection
        if  $_xHashCollection.Item ($vKey)=$value Then
            if $count=0 then $sItem= $vKey
            if $count<>0 then $sItem= $sItem&","&$vKey
            $count=$count+1
        EndIf
    Next
    $temparray=StringSplit($sItem,",")
    Return $temparray
EndFunc


; #FUNCTION# ====================================================================================================================
; Name...........: x
; Description ...: Gets or sets a value in an Associative Array
; Syntax.........: SET: x( $sKey , $vValue )
;                  GET: x( $key )
; Parameters ....: $sKey - the key to set or get. Examples:
;                  x( 'foo' )           gets value of foo
;                  x( 'foo.bar' )       gets value of bar which is a key of foo
;                  $bar = "baz"
;                  x( 'foo.$bar' )      gets value of baz which is a key of foo (variables are expanded)
; Return values .: Success - When setting, return the value set. When getting, returns the requested value.
;                  Failure - Returns a 0
; Author ........: OHB <me at orangehairedboy dot com>
; ===============================================================================================================================
Func x( $sKey = '' , $vValue = '' )
    if StringInStr($sKey,",") then
        $temparray=StringSplit($sKey,",")
        $sKey=$temparray[1]
        $vValue=$temparray[2]
    EndIf
        
    $func = "get"
    If @NumParams == 2 Then $func = "set"
    If $sKey == '' Then
        If $func == "get" Then
            Return $_xHashCollection
        Else
            $x_HashCollection = ObjCreate( "Scripting.Dictionary" )
            Return ''
        EndIf
    EndIf
    $parts = StringSplit( $sKey , "." )
    $cur = $_xHashCollection
    For $x = 1 To $parts[0] - 1
        If StringLeft( $parts[$x] , 1 ) == "$" Then $parts[$x] = Eval( $parts[$x] )
        If Not $cur.exists( $parts[$x] ) Then
            If $func == "get" Then Return
            $cur.add( $parts[$x] , ObjCreate( "Scripting.Dictionary" ) )
        EndIf
        $cur = $cur.item( $parts[$x] )
    Next
    $last_key = $parts[$parts[0]]
    If $func == "get" Then
        If Not $cur.exists( $last_key ) Then Return
        Return $cur.item( $last_key )
    ElseIf Not $cur.exists( $last_key ) Then
        $cur.add( $last_key , $vValue )
    Else
        $cur.item( $last_key ) = $vValue
    EndIf
    Return $vValue
EndFunc

; #FUNCTION# ====================================================================================================================
; Name...........: x_display
; Description ...: Displays the contents of an Associative Array
; Syntax.........: x_display( $sKey )
; Parameters ....: $sKey - the key to display. Examples:
;                  x_display()          displays everything
;                  x_display( 'foo' )   displays the contents of foo
; Author ........: OHB <me at orangehairedboy dot com>
; ===============================================================================================================================
Func x_display( $key = '' )
    $text = $key
    If $key <> '' Then $text &= " "
    $text &= _x_display( x( $key ) , '' )
    $wHnd = GUICreate( "Array " & $key , 700 , 500 )
    GUISetState( @SW_SHOW , $wHnd )
    $block = GUICtrlCreateEdit( $text , 5 , 5 , 690 , 490 )
    GUICtrlSetFont( $block , 10 , 400 , -1 , 'Courier' )
    While 1
        If GUIGetMsg() == $GUI_EVENT_CLOSE Then ExitLoop
    WEnd
    GUISetState( @SW_HIDE , $wHnd )
    GUIDelete( $wHnd )
EndFunc

; #INTERNAL_USE_ONLY# ===========================================================================================================
; Name...........: _x_display
; Description ...: Itterates through an array and builds output for x_display
; Author ........: OHB <me at orangehairedboy dot com>
; ===============================================================================================================================
Func _x_display( $item , $tab )
    If IsObj( $item ) Then
        $text = '[' & @CRLF
        For $i In $item
            $text &= $tab & "  " & $i & " " & _x_display( $item.item($i) , $tab & "  " )
        Next
        $text &= $tab & ']'
    Else
        $text = "=> '" & $item & "'"
    EndIf
    $text &= @CRLF
    Return $text
EndFunc

Also see this post

Share this post


Link to post
Share on other sites

Hi again

Not my best work, but am out of time. Here is where I think you could go with this....

I'm not sure where you're trying to go with that. Best I can tell is that perhaps you didn't understand the purpose of the functions. The point is to get rid of regular arrays altogether...at least when an associative array will work better. In addition, the ability to store data in a multi-dimensional structure is quite valuable. From what I can tell, your script was an example for how to store a list of trees and the type of tree it is. Here's how you should do it:

x( 'Tree.Pine.type' , 'Conifer' )
x( 'Tree.Spruce.type' , 'Conifer' )
x( 'Tree.Larch.type' , 'Conifer' )
x( 'Tree.Fir.type' , 'Conifer' )
x( 'Tree.Popular.type' , 'Deciduous' )
x( 'Tree.Birch.type' , 'Deciduous' )
x( 'Tree.Maple.type' , 'Deciduous' )
x( 'Tree.Oak.type' , 'Deciduous' )
x( 'Tree.Ash.type' , 'Deciduous' )
x( 'Tree.Basswood.type' , 'Deciduous' )
x( 'Tree.Pine.type' , 'Deciduous' )
x_display()
For $tree in x( 'Tree' )
    ConsoleWrite( $tree & " tree is " & x( 'Tree.'&$tree&'.type' ) & @CRLF )
Next

Check out the x_display() popup to see how the data is organized. Using this method requires you to give some thought to what data you want to store, how you want to store it, all in relation to how you will use it and how you need to get the data. But once you get over that little hurdle, you'll find such a mechanism invaluable.

Share this post


Link to post
Share on other sites

Thanks OHB, that add clarity. I may be missing the point and be stuck in the normal array methods. I put forward those methods because many of the functions I commonly use in autoit for data manipulation either require an array or produce an array in the standard format so it is advantageous to easily convert. Some examples

Inireadsection; inireadsectionnames; stringsplit; _ExcelReadArray; _ExcelReadSheetToArray

I am also struggling with having to know the tree name before I can query it's type. Not belittling your work, just trying to learn another data manipulation technique.

Cheers.

Picea892

Share this post


Link to post
Share on other sites

I put forward those methods because many of the functions I commonly use in autoit for data manipulation either require an array or produce an array in the standard format so it is advantageous to easily convert. Some examples

Inireadsection; inireadsectionnames; stringsplit; _ExcelReadArray; _ExcelReadSheetToArray

And that's great...I will hopefully be adding additional functionality down the line to provide some interoperability.

I am also struggling with having to know the tree name before I can query it's type.

Think of it this way: You can't open a file unless you first know where the file is. So, you need to know the folder before you can open the file. It's the same principle. The file belongs to that folder. In that same way, "type" is a property of "tree" and therefore belongs to "tree".

Using associative arrays isn't necessarily always the best thing...it depends on the data you're storing. If you need to store objects with properties, then this is the way to go. I used this to provide an easy way to localize an application that I'm working on. Here's an example:

x( "localization.en.lang_name","English")
x( "localization.en.i_speak","I speak English")
x( "localization.fr.lang_name","Français")
x( "localization.fr.i_speak","Je parle français")
x( "localization.es.lang_name","Español")
x( "localization.es.i_speak","Hablo español")

; start with English
Global $currentLanguage = "en"
MsgBox( 0 , getLocalized( 'lang_name' ) , getLocalized( 'i_speak' ) )

; change the language to French
$currentLanguage = "fr"
MsgBox( 0 , getLocalized( 'lang_name' ) , getLocalized( 'i_speak' ) )

; change the language to Spanish
$currentLanguage = "es"
MsgBox( 0 , getLocalized( 'lang_name' ) , getLocalized( 'i_speak' ) )

; let's look at the structure of the stored data
x_display()

Func getLocalized( $label )
    Return x( 'localization.'&$currentLanguage&'.'&$label )
EndFunc

My localization file is about 800 lines and has content for 14 languages. Adding a new language is as simple as adding new lines to the localization file - the program adapts automatically. I also don't have to mess with defining arrays, numbers, or UBound. Just makes life simpler!

Share this post


Link to post
Share on other sites

Very nice!! I've learnt something, and I'm going to use this in two of my projects.

Share this post


Link to post
Share on other sites

I'm not sure "x" is the right name for the function. It's too short, doesn't describe anything and the chance is very high someone wants to create their own function called that way.

Share this post


Link to post
Share on other sites

I'm not sure "x" is the right name for the function. It's too short, doesn't describe anything and the chance is very high someone wants to create their own function called that way.

Well, rename it if you want. Or suggest a new name. As I explained in my first post, I wanted to name that's short and simple. I'd also submit that if someone wants to create their own "x" function...they should also choose a better name.

Share this post


Link to post
Share on other sites

This is probably the most useful thing I've found in the forums from the time I started using autoit. Thank you, thank you, thank you.

A single function, and only 31 lines at that? Jesus :) I'm sorry, but I really don't get it, why this is not built-in into autoit by default?

PS. Such a small snippet, but would have saved me days, or even weeks of misery, now that I think about it.

@OHB :idea:

Share this post


Link to post
Share on other sites

Perhaps a typo -

Else
    $x_HashCollection = ObjCreate( "Scripting.Dictionary" )
    Return ''
EndIf

Shouldn't the var be $_xHashCollection? Otherwise that Else block doesn't do anything except return a blank string... (keep in mind I didn't do any in depth analysis here yet...)

Share this post


Link to post
Share on other sites

#14 ·  Posted (edited)

This is probably the most useful thing I've found in the forums from the time I started using autoit. Thank you, thank you, thank you.

A single function, and only 31 lines at that? Jesus :) I'm sorry, but I really don't get it, why this is not built-in into autoit by default?

PS. Such a small snippet, but would have saved me days, or even weeks of misery, now that I think about it.

@OHB :idea:

Glad you like it!

x_display() has been updated so you can new view the contents of regular arrays that are stored using this method. Try:

Local $myArray[2][2] = [[1,2],[3,4]]
x( 'some.key' , $myArray )
x_display()

Perhaps a typo -

Else
    $x_HashCollection = ObjCreate( "Scripting.Dictionary" )
    Return ''
EndIf

Shouldn't the var be $_xHashCollection? Otherwise that Else block doesn't do anything except return a blank string... (keep in mind I didn't do any in depth analysis here yet...)

Typo fixed and line changed to utilize removeAll instead. Thanks! :( Edited by OHB

Share this post


Link to post
Share on other sites

After update:

C:\xHashCollection.au3(37,70) : WARNING: $_xHashCache: possibly used before declaration.
    If IsDeclared("_xHashCache") And $_xHashCache.exists($all_but_last) Then
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
C:\xHashCollection.au3(37,70) : ERROR: $_xHashCache: undeclared global variable.
    If IsDeclared("_xHashCache") And $_xHashCache.exists($all_but_last) Then
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
C:\test.au3 - 1 error(s), 1 warning(s)
!>21:00:14 AU3Check ended.rc:2
>Exit code: 0    Time: 2.240

Share this post


Link to post
Share on other sites

#16 ·  Posted (edited)

After update:

C:\xHashCollection.au3(37,70) : WARNING: $_xHashCache: possibly used before declaration.
    If IsDeclared("_xHashCache") And $_xHashCache.exists($all_but_last) Then
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
C:\xHashCollection.au3(37,70) : ERROR: $_xHashCache: undeclared global variable.
    If IsDeclared("_xHashCache") And $_xHashCache.exists($all_but_last) Then
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
C:\test.au3 - 1 error(s), 1 warning(s)
!>21:00:14 AU3Check ended.rc:2
>Exit code: 0    Time: 2.240

That's an issue with AU3Chcek - it isn't smart enough to know that there isn't actually an error. To get around that, I've modified the code so it doesn't use Assign. Edited by OHB

Share this post


Link to post
Share on other sites

Typo fixed and line changed to utilize removeAll instead. Thanks! :idea:

I was about to suggest you changed $GUI_EVENT_CLOSE to -3 and ask about clearing the object :)

Would it be possible to clear only a part of the array? ie:

tree.key1
tree.key2
tree.key3
tree.key2.something
tree.key2.somethingelse

Would be good to clear tree.key2, so it leaves:
tree.key1
tree.key2
tree.key3

Or maybe it would leave only:
tree.key1
tree.key3

I don't know how these things work, its completely new to me, but I LOVE it.

And thanks again.

Share this post


Link to post
Share on other sites

#18 ·  Posted (edited)

That's an issue with AU3Chcek - it isn't smart enough to know that there isn't actually an error. To get around that, I've modified the code so it doesn't use Assign.

Yep, they work, it's just scite is whining all the time.

Edit: thanks for modifying that. ...I can't keep up with the updates :idea:

Edited by shEiD

Share this post


Link to post
Share on other sites

I was about to suggest you changed $GUI_EVENT_CLOSE to -3 and ask about clearing the object :idea:

Would it be possible to clear only a part of the array? ie:

tree.key1
tree.key2
tree.key3
tree.key2.something
tree.key2.somethingelse

Would be good to clear tree.key2, so it leaves:
tree.key1
tree.key2
tree.key3

Or maybe it would leave only:
tree.key1
tree.key3

I don't know how these things work, its completely new to me, but I LOVE it.

And thanks again.

Right now you can do x( 'tree.key2' , '' ) and the contents of key2 would be erased, but the key would still be there. I'll be adding an unset function soon :)

Share this post


Link to post
Share on other sites

#20 ·  Posted (edited)

Note: I've just removed the caching function because of an unforeseen error. On the plus-side, there's now an x_unset() function.

x( 'foo' , 'bar' )
x( 'yada' , 'yada' )
x( 'bar.foo' , 'bla' )
x_display()
x_unset( 'bar' )
x_display()

Grab your updated code above.

Edited by OHB

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now