dependent dictionaries excel vba - excel

I’d like to know which is the quickest way to get the unique values from a column and then the unique values in another column for each of the values previously found in the first column
Example
Column A Column B
Case 1 Item A
Case 1 Item B
Case 1 Item A
Case 2 Item C
Case 2 Item C
Case 3 Item D
Case 3 Item E
Case 3 Item F
Case 3 Item D
The result should return three values from the first column (Case 1, Case 2, Case 3) and then two values for Case 1 (Item A and Item B), one value for Case 2 (Item C), three values for Case 3 (Item D, Item E, Item F)
I do not want to use an advance filter or copy filtered rows in another sheet.
I tried to reach that using scripting dictionary, but I don’t know dictionary so well, and I was not able to use the keys of the first dictionary (Case 1, …) as parameters to add the items in the second dictionary (Item A, ….)
Ideally, at the end, the macro will create one textbox for each key of the first dictionary and then for each of those creates other text boxes for each key of the second dictionary (I kind of treeview but using textboxes)
Clearly, there will be a loop
Here one of the many tentatives (but, as I said, I have really poor knowledge in dictionary)
Dim d As Variant, dict As Object
Dim v As Long, a As Variant
Dim vCount As Long
Dim vCount1 As Long
Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare 'default is vbbinarycompare
With Sheets("Sheet1") '<- alter to suite
a = .Range("a2", Range("a" & Rows.Count).End(xlUp)).Value
' change "a1"/ "a" to appropreate column reference
'build dictionary
For v = LBound(a, 1) To UBound(a, 1)
'overwrite method - faster (no error control)
'writes name&position as key, ID as item
'dict.Itema(v, 1)(Join(Array(vVALs(v, 2)
dict.Item(Join(Array(a(v, 1)), ChrW(8203))) = a(v, 2)
Next v
Me.ComboBox1.List = dict.Keys
Me.ComboBox2.List = dict.Values
'loop through the second table
For v = 2 To .Cells(Rows.Count, 2).End(xlUp).row
d = (Join(Array(a(v, 1))))
If dict.Exists(d) Then
vCount = dict.Item(d)
MsgBox vCount
End If
Next v
End With
What if there is a third column ?
Example
Column A Column B Column C
Case 1 Item A
Case 1 Item B number 1
Case 1 Item A number 1
Case 2 Item C number 2
Case 2 Item C number 1
Case 3 Item D number 3
Case 3 Item E number 1
Case 3 Item F number 1
Case 3 Item D number 2
the result should be
Case 1
Item A number1
Item B number1
Case 2
Item C number1
number2
Case 3
Item D number2
number3
Item E number1
Item F number1
here the code I tried to build based on Zev Spitz suggestion, but without success
Dim row As Variant
Dim dict As New Dictionary
For Each row In Sheets("Positioning").Range("h2", Range("p" &
Rows.Count).End(xlUp)).Rows
Dim caseKey As String
caseKey = row.Cells.Item(2, 1).Value
Dim innerDict As Scripting.Dictionary
If dict.Exists(caseKey) Then
Set innerDict = dict(caseKey)
Else
Set innerDict = New Scripting.Dictionary
Set dict(caseKey) = innerDict
End If
innerDict(row.Cells.Item(2, 3).Value) = 1
Dim outerKey As Variant, innerKey As Variant, inner2Key As Variant
Dim inner2Dict As Scripting.Dictionary
For Each innerKey In innerDict.Keys
Set inner2Dict = New Scripting.Dictionary
If inner2Dict.Exists(inner2Dict) Then
Set innerDict(innerKey) = inner2Dict
Else
Set inner2Dict = inner2Dict
End If
inner2Dict(row.Cells.Item(2, 8).Value) = 1
Next
Next
For Each outerKey In dict.Keys
Debug.Print outerKey
For Each innerKey In innerDict.Keys
Debug.Print vbTab, innerKey
For Each inner2Key In inner2Dict.Keys
Debug.Print vbTab, vbTab, inner2Key
Next
Next
Next

Loading the data using nested dictionaries
You can use a dictionary which has other dictionaries as its' values:
Dim row As Variant
Dim dict As New Dictionary
For Each row In Worksheets("Sheet1").Range("A1", "B9").Rows
Dim caseKey As String
caseKey = row.Cells(1, 1).Value
Dim innerDict As Scripting.Dictionary
If dict.Exists(caseKey) Then
Set innerDict = dict(caseKey)
Else
Set innerDict = New Scripting.Dictionary
Set dict(caseKey) = innerDict
End If
innerDict(row.Cells(1, 2).Value) = 1 'an arbitrary value
Next
Then you can iterate over each key in the outer dictionary, and iterate over each key in the inner dictionary. The following code, for example:
Dim outerKey As Variant, innerKey As Variant
For Each outerKey In dict.Keys
Debug.Print outerKey
For Each innerKey In dict(outerKey).Keys
Debug.Print vbTab, innerKey
Next
Next
will output the following:
Case 1
Item A
Item B
Case 2
Item C
Case 3
Item D
Item E
Item F
For an description of how to use a dictionary to get a unique set of values, see here.
Populating another combobox based on the selection in the first combobox
Assuming you've set the List property of the first combobox to the Keys collection of the dictionary:
Me.ComboBox1.List = dict.Keys
you can handle the Change event of the combobox, and use it to fill the second combobox with the keys of the corresponding inner dictionary:
Private Sub ComboBox1_Change()
If Value Is Nothing Then
Me.ComboBox2.List = Nothing
Exit Sub
End If
Me.ComboBox2.Value = Nothing
Me.ComboBox2.List = dict(Me.ComboBox1.Value).Keys
End Sub
Unique values using SQL
Another way to get the unique combinations of values might be to execute an SQL statement on the Excel worksheet:
SELECT DISTINCT [Column A], [Column B]
FROM [Sheet1$]
but this generally comes back as an ADO or DAO flat Recordset -- with fields and rows -- while nested dictionaries preserve the hierarchical nature of this data.
Complete code-behind
Add a reference to the Microsoft Scripting Runtime (Tools > References...)
Option Explicit
Dim dict As New Dictionary
Private Sub ComboBox1_Change()
If Value Is Nothing Then
Me.ComboBox2.List = Nothing
Exit Sub
End If
Me.ComboBox2.Value = Nothing
Me.ComboBox2.List = dict(Me.ComboBox1.Value).Keys
End Sub
Private Sub UserForm_Initialize()
For Each row In Worksheets("Sheet1").Range("A1", "B9").rows
Dim caseKey As String
caseKey = row.Cells(1, 1).Value
Dim innerDict As Dictionary
If dict.Exists(caseKey) Then
Set innerDict = dict(caseKey)
Else
Set innerDict = New Dictionary
Set dict(caseKey) = innerDict
End If
innerDict(row.Cells(1, 2).Value) = 1 'an arbitrary value
Next
Me.ComboBox1.List = dict.Keys
End Sub
Complete code behind for two dependent comboboxes
Notice that the repetitious code has been (mostly) refactored into two methods: FindOrNew and HandleComboboxChange.
Option Explicit
Dim dict As New Dictionary
Private Function FindOrNew(d As Dictionary, key As String) As Dictionary
If d.Exists(key) Then
Set FindOrNew = d(key)
Else
Set FindOrNew = New Dictionary
Set d(key) = FindOrNew
End If
End Function
Private Sub HandleComboboxChange(source As ComboBox, target As ComboBox)
If source.Value Is Nothing Then
Set target.list = Nothing
Exit Sub
End If
Set target.Value = Nothing
End Sub
Private Sub ComboBox1_Change()
HandleComboboxChange ComboBox1, ComboBox2
ComboBox2.list = dict(ComboBox1.Value).Keys
End Sub
Private Sub ComboBox2_Change()
HandleComboboxChange ComboBox2, ComboBox3
ComboBox3.list = dict(ComboBox1.Value)(ComboBox2.Value).Keys
End Sub
Private Sub UserForm_Initialize()
For Each row In ActiveSheet.Range("A1", "C9").rows
Dim caseKey As String
caseKey = row.Cells(1, 1).Value
Dim itemKey As String
itemKey = rows.Cells(1, 2).Value
Dim dictLevel2 As Dictionary
Set dictLevel2 = FindOrNew(dict, caseKey)
Dim innerDict As Dictionary
Set innerDict = FindOrNew(dictLevel2, itemKey)
innerDict(row.Cells(1, 3).Value) = 1 'an arbitrary value
Next
ComboBox1.list = dict.Keys
End Sub

Related

In excel VBA, how can you sort a dictionary by a field of the Item (similar to C# orderby)?

I have a dictionary in excel VBA where the key is a string (SS #) and the value is an object that has 3 properties (Name, Birthdate and Job Name)
Dim d as Dictionary
Set d = new Dictionary
d.Add "123", obj
d.Add "234", obj2
d.Add "342", obj3
I want to print out a table by in order of Birthdate. In C#, i would do something like this
for each (var item in dict.Items.Orderby(r=>r.Birthdate))
but i can't figure out in VBA how i can sort this dictionary by the Birthdate of the item in that dictionary.
Is this possible in Excel VBA?
Here's one approach:
Sub Tester()
Dim dict As Object, i As Long, dt As Date, itms, e
Set dict = CreateObject("scripting.dictionary")
'some test data
For i = 1 To 10
dt = Now - Application.RandBetween(500, 5000)
dict.Add "Object_" & i, GetTestObject("Name_" & i, dt, "Job_" & i)
Next i
itms = dict.items
'Stop
SortObjects itms, "BirthDate"
Debug.Print "---------Birthdate-------"
For Each e In itms
Debug.Print e.Name, e.BirthDate, e.JobName
Next e
SortObjects itms, "JobName"
Debug.Print "---------JobName-------"
For Each e In itms
Debug.Print e.Name, e.BirthDate, e.JobName
Next e
End Sub
Function GetTestObject(nm As String, dt As Date, jb As String)
Dim obj As New clsTest
obj.Name = nm
obj.BirthDate = dt
obj.JobName = jb
Set GetTestObject = obj
End Function
'Sort an array of objects using a given property 'propName'
Sub SortObjects(list, propName As String)
Dim First As Long, Last As Long, i As Long, j As Long, vTmp, oTmp As Object, arrComp()
First = LBound(list)
Last = UBound(list)
'fill the "compare" array...
ReDim arrComp(First To Last)
For i = First To Last
arrComp(i) = CallByName(list(i), propName, VbGet)
Next i
'now sort by comparing on `arrComp` not `list`
For i = First To Last - 1
For j = i + 1 To Last
If arrComp(i) > arrComp(j) Then
vTmp = arrComp(j) 'swap positions in the "comparison" array
arrComp(j) = arrComp(i)
arrComp(i) = vTmp
Set oTmp = list(j) '...and in the original array
Set list(j) = list(i)
Set list(i) = oTmp
End If
Next j
Next i
End Sub
A Dictionary cannot be sorted in the way you suggest out of the box.
Aside from the suggestions proposed by others in the comments, you could consider using an ADODB.Recordset which has rich and fast sorting features.
Please refer to this website for further guidance ADODB.Recordset
This way you loose some handy features of a Dictionary, but for your purpose, I understand you don't need them.

Combo boxes where available choices are unique and dependent on the choice in previous combo box

I have a data set in another file that has 3 columns with thousands of rows. All 3 columns have values that are not unique.
I need 3 combo boxes.
The first combo box is for selecting from column "A" (bringing back unique values) for the different types of business units.
Next, depending on the business unit, combo box 2 is for selecting a specific customer (depending on the business unit selected).
Finally, combo box 3 is for selecting from the different cost centers that exist for a given customer.
I need unique values for all 3 columns.
I think I have combo box 1 with the following code:
Option Explicit
Private Sub UserForm_Initialize()
Dim wbExternal As Workbook '<-- the other workbook with the data
Dim wsExternal As Worksheet '<-- the worksheet in the other workbook
Dim lngLastRow As Long '<-- the last row on the worksheet
Dim rngExternal As Range '<-- range of data for the RowSource
Dim myCollection As collection, cell As Range
On Error Resume Next
Application.ScreenUpdating = False
Set wbExternal = Application.Workbooks.Open("C:\Users\sarabiam\desktop\OneFinance_Forecast_Model\FY19_New_Forecast_Model_Data_Tables.xlsm", True, True)
Set wsExternal = wbExternal.Worksheets("#2Table_Revenue") '<-- identifies worksheet
Set rngExternal = wsExternal.Range("A8:A" & CStr(lngLastRow))
Set myCollection = New collection
With ComboBox1
.Clear
For Each cell In Range("A8:A" & Cells(Rows.Count, 1).End(xlUp).Row)
If Len(cell) <> 0 Then
Err.Clear
myCollection.Add cell.Value, cell.Value
If Err.Number = 0 Then .AddItem cell.Value
End If
Next cell
End With
ComboBox1.ListIndex = 0
wbExternal.Close
Application.ScreenUpdating = True '<-- updates the worksheet on your screen
any time there is a change within the worksheet
End Sub
Here's a pretty generic approach - it only loads the data once, into an array, then uses that to reset list content on selection of a "previous" list.
Option Explicit
Const dataPath As String = "C:\Users\usernameHere\Desktop\tmp.xlsx"
Dim theData 'source data
Private Sub UserForm_Activate()
LoadData
Me.cboList1.List = GetList(1, "")
End Sub
Private Sub cboList1_Change()
Me.cboList2.Clear
Me.cboList2.List = GetList(2, Me.cboList1.Value)
Me.cboList3.Clear
End Sub
Private Sub cboList2_Change()
Me.cboList3.Clear
Me.cboList3.List = GetList(3, Me.cboList2.Value)
End Sub
'Return unique values from source data, given a specific column
' If given a value for "restrictTo", filter on match in column to "left"
' of the requested value column
Function GetList(colNum As Long, restrictTo)
Dim i As Long, n As Long, rv()
Dim dict As Object, v, ub As Long, inc As Boolean
Set dict = CreateObject("scripting.dictionary")
ub = UBound(theData, 1)
ReDim rv(1 To ub) 'will set final size after filling...
n = 0
For i = 1 To ub
v = theData(i, colNum)
'are we restricting the values we collect based on a different list?
If colNum > 1 And Len(restrictTo) > 0 Then
'is this value valid?
inc = (theData(i, colNum - 1) = restrictTo)
Else
inc = True 'collect all values
End If
If inc And Not dict.exists(v) Then
'don't already have this value - add to array and dict
n = n + 1
dict.Add v, True
rv(n) = v
End If
Next i
ReDim Preserve rv(1 To n) 'resize array to size of content
GetList = rv
End Function
'load data from external file
Private Sub LoadData()
With Workbooks.Open(dataPath).Worksheets("#2Table_Revenue")
theData = .Range(.Range("A8"), _
.Cells(.Rows.Count, 1).End(xlUp).Offset(0, 2)).Value
.Parent.Close False
End With
End Sub

Turn a sub into a sub with arguments

new to VBA and i have a bit of problems with a sub I wrote.
This sub takes values from various coloumns and put the values into a dictionary, then prints the dictionary in another coloumn.
Sub Unitario()
Dim Dict As Object
Dim bRiga As Long
Dim aRiga As Long
Dim cRiga As Long
Dim dRiga As Long
Dim I As Long
Dim MyString As String
Dim arr
Set Dict = CreateObject("Scripting.Dictionary")
Dict.CompareMode = vbTextCompare 'compare without distinction between capitals
'while vbBinaryCompare distinguish between capitals
ThisWorkbook.Worksheets("Foglio2").Range("c1").EntireColumn.Clear
aRiga = Sheets("Lavoro").Cells(Rows.Count, "M").End(xlUp).Row
bRiga = Sheets("Lavoro").Cells(Rows.Count, "N").End(xlUp).Row
cRiga = Sheets("Lavoro").Cells(Rows.Count, "O").End(xlUp).Row
dRiga = Sheets("Lavoro").Cells(Rows.Count, "P").End(xlUp).Row
For I = 4 To aRiga
MyString = Sheets("Lavoro").Cells(I, "M")
'to change coloumn i need to change values up there
If Not Dict.exists(MyString) Then
Dict.Add MyString, MyString
End If
Next I
'adds coloumns value to dictionary
For I = 4 To bRiga
MyString = Sheets("Lavoro").Cells(I, "N")
'to change coloumn i need to change values up there
If Not Dict.exists(MyString) Then
Dict.Add MyString, MyString
End If
Next I
'adds coloumns value to dictionary
For I = 4 To cRiga
MyString = Sheets("Lavoro").Cells(I, "O")
'to change coloumn i need to change values up there
If Not Dict.exists(MyString) Then
Dict.Add MyString, MyString
End If
Next I
'adds coloumns value to dictionary
For I = 4 To dRiga
MyString = Sheets("Lavoro").Cells(I, "P")
'to change coloumn i need to change values up there
If Not Dict.exists(MyString) Then
Dict.Add MyString, MyString
End If
Next I
'adds coloumns value to dictionary
arr = Dict.Items
Worksheets("Foglio2").Range("c1").Resize(Dict.Count, 1).Value = Application.Transpose(arr)
End Sub
it is clear that this sub is not optimized, since i have to manually change the values in the sub anytime i have to use it with other ranges.
what i'm trying to do is make a sub that can be called with various range arguments from buttons, without having to write 100 times the same macro with different ranges.
so that i could simply write something like this instead of manually modifying the code:
Private sub Commandbutton1_Click
Unitario(OutputSheet,OutputCell,InputRange1,InputRange2,..., InputRangeN)
End Sub
so that i have only 1 macro on the excel and various buttons with different arguments.
can you help me?
It can be like below:
Sub Unitario(strFirstCol as String,strSecondCol as String, strThirdCol as String, strFourthCol as String)
And then you will have to adopt the following section.
aRiga = Sheets("Lavoro").Cells(Rows.Count, strFirstCol).End(xlUp).Row
bRiga = Sheets("Lavoro").Cells(Rows.Count, strSecondCol).End(xlUp).Row
cRiga = Sheets("Lavoro").Cells(Rows.Count, strThirdCol).End(xlUp).Row
dRiga = Sheets("Lavoro").Cells(Rows.Count, strFourthCol).End(xlUp).Row
Inside each "For loop":
MyString = Sheets("Lavoro").Cells(I, strFirstCol) '\\ Column M
MyString = Sheets("Lavoro").Cells(I, strSecondCol) '\\ Column N
MyString = Sheets("Lavoro").Cells(I, strThirdCol) '\\ Column O
MyString = Sheets("Lavoro").Cells(I, strFourthCol) '\\ Column P
And then call the sub like
Call Unitario("M","N","O","P")
whenever i have to add arguments to a often used sub, or function, i just add the arguments with ´optionaĺ.
This way i won ´t have to recode every call to the sub.
Example
Public sub test (byval optional addr as string)

Looping over each vba collection failing conflicting variable types

I'm new to VBA and am picking up pieces. I'm having a problem understanding the collection created and then looping over it to read the values against each key one at a time. My code is below.
Excel Data I'm using with the data
Suffice to say, the error I get in Excel 2016 when running the module is:
Pressing Debug shows
I'm trying to print the values against the keys. I expected 80, 20 etc.. to be printed. Could someone please help me to understand why I'm wrong in writing dataItems and how to resolve so it prints the values agains the keys - I suspect it's a for loop that's needed.
Any help would be appreciated.
Code I'm using:
Class : CItems
Option Explicit
Public Key As String
Public Sum As Long
Public Count As Long
Public ItemList As Collection
Public Function Mean() As Double
Mean = Sum / Count
End Function
Private Sub Class_Initialize()
Sum = 0
Count = 0
Set ItemList = New Collection
End Sub
Module: m_Call
Option Explicit
Sub m_Call()
''' Create Collection from Column A and B in worksheet called RAW_DATA
Dim col As Collection
Dim dataItems As cItems
Dim itemKey As String
Dim item1 As Long
Dim ws As Worksheet
Dim r As Long
Set ws = ThisWorkbook.Worksheets("RAW_DATA")
Set col = New Collection
For r = 2 To 3000
itemKey = CStr(ws.Cells(r, "A").Value2) '~~adjust to your own column(s)
item1 = CLng(ws.Cells(r, "B").Value2) '~~adjust to your own column(s)
'Check if key already exists
Set dataItems = Nothing: On Error Resume Next
Set dataItems = col(itemKey): On Error GoTo 0
'If key doesn't exist, create a new class object
If dataItems Is Nothing Then
Set dataItems = New cItems
dataItems.Key = itemKey
col.Add dataItems, itemKey
End If
'Add cell values to the class object
With dataItems
.Sum = .Sum + item1
.Count = .Count + 1
.ItemList.Add item1
End With
Next
'Iterating through all of the items
Dim i As Long
i = 5
For Each dataItems In col
Debug.Print dataItems.Mean
ws.Cells(5, i) = dataItems.Key
' read in column 5 and check search each cells content to see if it matches a collection key's string.
i = i + 1
Next
'Selecting one item
'Set dataItems = col("PersonA 1")
'ws.Cells(4, 5) = dataItems.Mean
''' Read excel and populate categories if the value in a column A cell matches with a key in the Collection.
''' Column 10 and 11 should have the values that match each Key inserted respectively.
Dim cols As Range
Dim rng As Range
Dim currentRow As Long
Dim category As Variant
' Use a With block to 'Fully Qualify' the ranges to MySheet
With ThisWorkbook.Sheets("RAW_DATA")
' Set col range to the intersection of used range and column B
Set cols = Intersect(.UsedRange, .Columns("A"))
' Loop through cells in cols to set description in each row
For Each rng In cols
currentRow = rng.Row
' Read in key's from collection
For Each dataItems In col
' read in column and search each cells content to see if it matches a collection key's string.
.Cells(currentRow, 10) = rng.Value
If rng.Value = dataItems.Key Then
.Cells(currentRow, 10) = "Working"
'Debug.Print dataItems
'''Need to insert value1 from key into Column 10 and value2 from same key into column 11.
''' I'm just testing to see if I can insert a single category first before working on the loop.
.Cells(currentRow, 10) = "Shopping"
.Cells(currentRow, 11) = dataItems
End If
Next
Next rng
End With
''' End of Read excel
End Sub
I really cannot get what you want to achieve. Also, I have the vague idea that you are overthinking your problem. With those concerns in mind, try the following and see if it helps you.
If rng.Value = dataItems.Key Then
.Cells(currentRow, 10) = "Working"
'Debug.Print dataItems
'''Need to insert value1 from key into Column 10 and value2 from same key into column 11.
''' I'm just testing to see if I can insert a single category first before working on the loop.
.Cells(currentRow, 10) = "Shopping"
For k = 1 To dataItems.Count
.Cells(currentRow, k + 11) = dataItems.ItemList(k)
Next
End If
Also, try to use the Watch Window, adding dataItems as the inspected variable. Insert a Breakpoint in your code (for example in If rng.Value = dataItems.Key Then), and step on with F8.

Excel vba nested dictionary - accessing items

Tim is it possible to extract a list of row keys from the clsMatrix class? something like this...
Sub KEYS()
Dim KEY_LIST As Variant
KEY_LIST = TABLES("UDLY").dR.KEYS
End Sub
I can then cycle through a table to extract a subset of data which meet a certain criteria.
Tim, your code works well for one 2D matrix, but I have 5 tables to reference for the project to work. I tried using if...then else statements, but it's clumsy and doesn't work - the second pass looking for data from the BOOK table can't find the row and col dictionary references. Can you suggest a better method? Thanks for your help.
Option Explicit
Private dR, dC
Private m_arr, UDLY, BOOK
'
Sub Init(TABLE As String)
Dim i As Long
Dim RNGE As Range
Dim DATA As Variant
Dim arr As Variant
If TABLE = "UDLY" Then Set RNGE = Worksheets("SETTINGS").Range("UDLY_TABLE")
If TABLE = "BOOK" Then Set RNGE = Worksheets("BOOK").Range("BOOK_TABLE")
arr = RNGE.Value
Set dR = CreateObject("Scripting.Dictionary")
Set dC = CreateObject("Scripting.Dictionary")
'add the row keys and positions
For i = LBound(arr, 1) + 1 To UBound(arr, 1)
dR.Add arr(i, 1), i
Next i
'add the column keys and positions
For i = LBound(arr, 2) + 1 To UBound(arr, 2)
dC.Add arr(1, i), i
Next i
' m_arr = arr
If TABLE = "UDLY" Then UDLY = arr
If TABLE = "BOOK" Then BOOK = arr
End Sub
Function GetValue(TABLE, rowKey, colKey)
If dR.Exists(rowKey) And dC.Exists(colKey) Then
' GetValue = m_arr(dR(rowKey), dC(colKey))
If TABLE = "UDLY" Then GetValue = UDLY(dR(rowKey), dC(colKey))
If TABLE = "BOOK" Then GetValue = BOOK(dR(rowKey), dC(colKey))
Else
GetValue = 999 '"" 'or raise an error...
End If
End Function
'===========================================================
Option Explicit
Sub Tester()
Dim m As New clsMatrix
' m.Init (ActiveSheet.Range("b40").CurrentRegion.Value)
' m.Init (Worksheets("settings").Range("udly_table"))
m.Init ("UDLY")
Debug.Print m.GetValue("UDLY", "APZ4-FUT", "SPOT_OFFLINE")
m.Init ("BOOK")
Debug.Print m.GetValue("BOOK", "2.04", "STRIKE")
End Sub
Sub DICT_OF_DICT()
Dim d1, d2
Set d1 = CreateObject("Scripting.Dictionary")
Set d2 = CreateObject("Scripting.Dictionary")
d1.Add "BPH", "Hello"
d2.Add "Shaun", d1
Debug.Print d2("Shaun").Item("BPH")
End Sub
EDIT: if I wanted to deal with quickly accessing a 2-D array using row/column headers then I'd be inclined not to use nested dictionaries, but to use two distinct dictionaries to key into each dimension (a "row label" dictionary and a "column label" one).
You can wrap this up in a simple class:
'Class module: clsMatrix
Option Explicit
Private dR, dC
Private m_arr
Sub Init(arr)
Dim i As Long
Set dR = CreateObject("Scripting.Dictionary")
Set dC = CreateObject("Scripting.Dictionary")
'add the row keys and positions
For i = LBound(arr, 1) + 1 To UBound(arr, 1)
dR.Add arr(i, 1), i
Next i
'add the column keys and positions
For i = LBound(arr, 2) + 1 To UBound(arr, 2)
dC.Add arr(1, i), i
Next i
m_arr = arr
End Sub
Function GetValue(rowKey, colKey)
If dR.Exists(rowKey) And dC.Exists(colKey) Then
GetValue = m_arr(dR(rowKey), dC(colKey))
Else
GetValue = "" 'or raise an error...
End If
End Function
'EDIT: added functions to return row/column keys
' return a zero-based array
Function RowKeys()
RowKeys = dR.Keys
End Function
Function ColumnKeys()
ColumnKeys = dC.Keys
End Function
Example usage: assuming A1 is the top-left cell in a rectangular range where the first row is column headers ("col1" to "colx") and the first column is row headers ("row1" to "rowy") -
EDIT2: made some changes to show how to manage multiple different tables (with no changes to the class code)
'Regular module
Sub Tester()
Dim tables As Object, k
Set tables = CreateObject("Scripting.Dictionary")
tables.Add "Table1", New clsMatrix
tables("Table1").Init ActiveSheet.Range("A1").CurrentRegion.Value
tables.Add "Table2", New clsMatrix
tables("Table2").Init ActiveSheet.Range("H1").CurrentRegion.Value
Debug.Print tables("Table1").GetValue("Row1", "Col3")
Debug.Print tables("Table2").GetValue("R1", "C3")
k = tables("Table1").RowKeys()
Debug.Print Join(k, ", ")
End Sub

Resources