I am using VBA to store key and value associated with the key into dictionary.
Sub Dict_Example()
Set dict = CreateObject("Scripting.Dictionary")
For i = 1 to 5
dict.Add i, some number
Next i
I would like to find the highest value and its associated key in dict.
For example, if dict = {1: 5, 2: 10, 3: 6, 4: 11, 5: 3} where 1,2,3,4,5 are keys and 5, 10, 6, 11, 3 are values then it should return 4:11.
How do I do this in VBA?
I would generate an array from the dict.items and use Max/Min function on that. Then loop keys and compare items against that.
Option Explicit
Public Sub Dict_Example()
Dim dict As Object, max As Long, min As Long, arr(), key As Variant, i As Long
Set dict = CreateObject("Scripting.Dictionary")
For i = 1 To 5
dict.Add i, i * Application.WorksheetFunction.RandBetween(0, 100)
Next i
max = Application.max(dict.items)
min = Application.min(dict.items)
For Each key In dict
Debug.Print key, dict(key)
If dict(key) = max Then Debug.Print "max= " & dict(key) & vbTab & "key= " & key
Next
Stop
End Sub
You could accomplish this by using a couple arrays to temporarily store your high and low values and associated keys as you iterated through the dictionary like this:
Sub test()
Dim dict As New Dictionary
Dim low(1 To 2)
Dim high(1 To 2)
Dim i As Long
Dim key
For i = 1 To 5
dict.Add i, 'some number
Next i
low(1) = dict.Keys(0)
low(2) = dict(dict.Keys(0))
high(1) = dict.Keys(0)
high(2) = dict(dict.Keys(0))
For i = 0 To dict.Count - 1
If dict(dict.Keys(i)) < low(2) Then
low(1) = dict.Keys(i)
low(2) = dict(dict.Keys(i))
ElseIf dict(dict.Keys(i)) > high(2) Then
high(1) = dict.Keys(i)
high(2) = dict(dict.Keys(i))
End If
Next i
Debug.Print low(1) & ":" & low(2) & vbCrLf & high(1) & ":" & high(2)
End Sub
But sorting like this would only work correctly for numeric values. #Ryan Wildry's comment is the way to go for generally sorting a dictionary, then you would grab your values using dict(dict.Keys(0)) and dict(dict.Keys(dict.Count - 1)) respectively where dict references your sorted dictionary.
EDIT:
You'll need to add a library reference to the Microsoft Scripting Runtime for this to work.
Related
I am new for VBA and I am trying to find the smallest and largest date which fulfill 2 criterias.
If I find a date by using non-vba method, I can use ={index(lookup value)match(true,(criteria1*criteria2),0))} to find out the date.
But, above function cannot find the smallest date and largest date, it can only show the first result in the table.
As you see, I want to find out Column D (early date) and Column E (deadline date) by criterias
"Area"
and
"Floor_Level"
I have no idea how to write the VBA code...
I only know I shall use function like this:
Function findvalue(ByVal info1 As String, ByVal info2 As String) As Variant
Sorry for the long question. I am learning the vba recently and a little bit difficult to find the solution like this case......
You can use Dictionary object. I make this code with dummy data and with a simple output. If the key (area+floor) has one date, the date repeats as min and max. If no, the left date is min, the right is max
Option Explicit
Sub MinMaxDates()
' set a reference to 'Microsoft Scripting Runtime' in Tools->References VBE menu
Dim dict As New Scripting.Dictionary, D As Variant, rng As Range, cl As Range, key As Variant, a, b, cnt As Long
Set rng = Intersect(Range("A1").CurrentRegion.Columns(1), Range("A1").CurrentRegion.Offset(1))
For Each cl In rng
key = cl.Text & "+" & cl.Offset(, 1).Text
D = cl.Offset(, 2).Value
If dict.Exists(key) Then
a = dict(key)
If a(0) > D Then a(0) = D
If a(1) < D Then a(1) = D
dict(key) = a
Else
dict.Add key, Array(D, D)
End If
Next
' output
Range("E1").Resize(, 4) = Array("Area", "Floor", "MinDate", "MaxDate")
cnt = 1
For Each key In dict.Keys
a = Split(key, "+")
b = dict(key)
If b(0) = b(1) Then b(1) = "No max date"
Range("E1").Offset(cnt).Resize(, 4) = Array(a(0), a(1), b(0), b(1))
cnt = cnt + 1
Next
End Sub
I am new to VBA macro and need some experts help on meeting the below requirement.
I got a workbook containing 2 sheets called 'Data' and 'Stats'.
'Data' contains the values as below
'Stats' contains the values as below
On click on the button, I would like to do the below
Get the values in column A in 'Stats' sheet
Find all the matching rows in 'Data' Sheet
Find the smallest start time and put that in 'Stats' sheet against the stage value
Find the biggest end time and that in 'Stats' sheet against the stage value
Final output would be like below
Note: I do not have the MINIFS or MAXIFS in my installation.
Incase you dont have MINIFS and MAXIFS you can use array formulas like so:
={MIN(IF(Stats!A1=Data!$A$1:$A$1000,Data!$C$1:$C$1000))}
and
={MAX(IF(Stats!A1=Data!$A$1:$A$1000,Data!$B$1:$B$1000))}
The {} indicates, that this is a Array-Formula. Enter with Ctrl + Shift + Enter
No VBA needed.
Just use in your Stats worksheet the following formula for Start:
=MINIFS(Data!A:A,Data!C:C,Stats!A:A)
and the following for End:
=MAXIFS(Data!B:B,Data!C:C,Stats!A:A)
Please, the VBA solution, too. It will be very fast, using arrays, processing everything in memory and dropping the result at once:
Sub BringStats()
Dim shD As Worksheet, shS As Worksheet, lastRD As Long, lastRS As Long
Dim arrD, arrS, i As Long, k As Long, dict As Object, El As Variant
Set shD = Worksheets("Data")
Set shS = Worksheets("Stats")
lastRD = shD.Range("A" & rows.count).End(xlUp).row
lastRS = shS.Range("A" & rows.count).End(xlUp).row
arrD = shD.Range("A2:C" & lastRD).Value
arrS = shS.Range("A2:C" & lastRS).Value
Set dict = CreateObject("Scripting.dictionary")
'load the dictionary with unique keys and all corresponding date in a string, as item
For i = 1 To UBound(arrD)
If Not dict.Exists(arrD(i, 3)) Then
dict.Add arrD(i, 3), CDate(arrD(i, 1)) & ";" & CDate(arrD(i, 2))
Else
dict(arrD(i, 3)) = dict(arrD(i, 3)) & "|" & CDate(arrD(i, 1)) & ";" & CDate(arrD(i, 2))
End If
Next
Dim arr As Variant, minTime As Date, minPos As Long
For i = 1 To UBound(arrS)
If dict.Exists(arrS(i, 1)) Then
arr = Split(dict(arrS(i, 1)), "|") 'extract each pair of time stamps
If UBound(arr) > 0 Then
For Each El In arr 'extract the element containing minimum time
If minTime = 0 Then
minTime = TimeValue(Split(El, ";")(0)): minPos = k
Else
If TimeValue(Split(El, ";")(0)) < minTime Then minTime = TimeValue(Split(El, ";")(0)): minPos = k
End If
k = k + 1
Next
arrS(i, 2) = Split(arr(minPos), ";")(0): arrS(i, 3) = Split(arr(minPos), ";")(1) 'load the array with the minimum time correspondent values
Else
arrS(i, 2) = Split(dict(arrS(i, 1)), ";")(0): arrS(i, 3) = Split(dict(arrS(i, 1)), ";")(1)'loading the array in case of only one occurrence
End If
End If
minPos = 0: minTime = 0: k = 0 'reinitialize the used variables
Next i
'drop the processed array at once
shS.Range("A2").Resize(UBound(arrS), UBound(arrS, 2)).Value = arrS
End Sub
There can be a lot of the same 'stage' occurrences...
I have two jagged arrays containing information on some entities that I need to compare.
Dim MyArray1()
Dim MyArray2()
MyArray1(0) = Array("ID1", 2)
MyArray1(1) = Array("ID2", 7)
MyArray1(2) = Array("ID3", 5)
MyArray1(3) = Array("ID4", 3)
MyArray2(0) = Array("ID1", 5)
MyArray2(1) = Array("ID2", 8)
MyArray2(2) = Array("ID3", 6)
MyArray2(3) = Array("ID4", 9)
I'm looking for the best way of comparing these arrays, in this case I will need to get both the number difference between them. So ID1 = 3, ID2 = 1, etc. Any assistance would be greatly appreciated!
If both arrays will always have same size, then maybe would be easier to use dictionaries.
Excel VBA Dictionary – A Complete Guide
Sub COMPARING_VALUES()
Dim Dict1 As Object
Dim Dict2 As Object
Set Dict1 = CreateObject("Scripting.Dictionary")
Set Dict2 = CreateObject("Scripting.Dictionary")
With Dict1
.Add "ID1", 2
.Add "ID2", 7
.Add "ID3", 5
.Add "ID4", 3
End With
With Dict2
.Add "ID1", 5
.Add "ID2", 8
.Add "ID3", 6
.Add "ID4", 9
End With
Dim key As Variant
For Each key In Dict1.Keys
Debug.Print Dict2(key) - Dict1(key)
Next key
End Sub
In this case, you can use Dictionaries because you are relating data with an ID (that means a key), not just their position in array, so probably it's easier to use dictionaries.
Executing this code will print in debugger these values:
3
1
1
6
Hope you can adapt this to your needs.
Sub compJaggedArrs()
Dim MyArray1(0 To 3)
Dim MyArray2(0 To 3)
Dim i As Long, j As Long
MyArray1(0) = Array("ID1", 2)
MyArray1(1) = Array("ID2", 7)
MyArray1(2) = Array("ID3", 5)
MyArray1(3) = Array("ID4", 3)
MyArray2(0) = Array("ID1", 5)
MyArray2(1) = Array("ID2", 8)
MyArray2(2) = Array("ID3", 6)
MyArray2(3) = Array("ID4", 9)
For i = 0 To UBound(MyArray1)
For j = 0 To UBound(MyArray2)
If MyArray1(i)(0) = MyArray2(j)(0) Then
Debug.Print MyArray1(i)(0) & " difference is: " & abs(MyArray1(i)(1) - MyArray2(j)(1))
End If
Next j
Next i
End Sub
Of course you can output the differences in another way. Just didn't know what you wanted to do with the results, it now prints to the debug window
I have code that loops through a dictionary. the value for each key in the dictionary is a 2-item array (dictionary looks like name: [string, integer]).
when I reference the dictionary later on, I can see and print the string and integer in the array belonging to the dictionary entry, but I can't change the integer through a normal assignment like dictionary(name)(2) = 5; after doing so in the code, I print the array value to a debug file, and the array value is the original value, not its changed value. I have no idea why this doesn't work, and how I can get it to work. everything I've read on arrays says you just assign array(0) = something.
here is part of the code below:
defining the original dictionary:
dim Dict as Object
Set Dict = CreateObject("Scripting.Dictionary")
for i = 1 to 10
strnum = "str"&i
Dict.Add strnum, array("str"&i+1,0)
next
'printing each item in dict returns "Str1: [str2,0]", etc. as it should
working with the dictionary:
For each Cell in Range("a1:a11")
If Cell.Value <> "" And Dict.exists(Cell.Value) Then
name = Cell.Value
range = Dict(name)(0)
If Dict(name)(1) = 1 Then
'we have already located the name here
Else
Dict(name)(1) = 1
s = "Setting the found flag to " & Dict(name)(1)
debug.print s
'Dict(name)(1) returns 0 when it should return 1
end if
end if
next cell
Range a1:a11 is Str1,Str2,Str3,Str4,Str5...Str11.
What can I do to fix this?
You are creating some weird aliasing with
for i = 1 to 10
Str = "str"&i
arr(1) = "str"&i+1
arr(2) = 0
Dict.Add Str, arr
next
since you only created a single array when you dimensioned arr.
You could create a dictionary of dictionaries to do what you wanted:
Sub test()
Dim Dict As Object, Str, i, arr
Set Dict = CreateObject("Scripting.Dictionary")
For i = 1 To 10
Set arr = CreateObject("Scripting.Dictionary")
arr.Add 1, "str" & i + 1
arr.Add 2, 0
Str = "str" & i
Dict.Add Str, arr
Next
For i = 1 To 10
Debug.Print (Dict("str" & i)(1))
Next i
Dict("str1")(1) = "Bob"
Debug.Print Dict("str1")(1) 'prints Bob
End Sub
I'd like to print nested dictionaries in VBA. Basically I have a Dictionary where every key is a String, but each value can either be a String or another Dictionary.
Say my Dictionary has got the value
{ "FOO" => "BAR" , "HELLO" => { "WORLD => ":)", "OTHER" => ":(" } }
I want to display in an Excel spreadsheet:
FOO |BAR |
HELLO|WORLD|:)
HELLO|OTHER|:(
My issue is that I need to find a way to guess what is the type of the value under each key, so when I call dict("HELLO") I can either display the value if it's a string or if it's a dictionary call the same function again.
In order to do so I need to know:
if there is a way to know what is the type of a value stored in a dictionary
if there is a way to cast that value to a target type (string or dictionary)
So here is what I've tried
Function display_dictionary(dict As Scripting.Dictionary, out As Range) As Integer
Dim vkey As Variant
Dim key As String
Dim row_offset As Integer
Dim value As Object
Dim svalue As String
Dim dvalue As Dictionary
Dim each_offset As Integer
row_offset = 0
For Each vkey In dict.Keys()
key = vkey
Set value = dict(key)
if value is String then
svalue = ???
out.offset(row_offset, 0).value = key
out.offset(row_offset, 1).value = svalue
row_offset = row_offset + 1
else if value is Dictionary
dvalue = ???
each_offset = display_dictionary(dvalue, out.offset(row_offset, 1))
For each_row = 0 To each_offset - 1
out.offset(row_offset + each_row) = key
Next
row_offset = row_offset + each_offset
End If
Next
End
I am actually going to propose a bit different way for displaying the results. I think it's more logical but you are welcome to modify it to suit your specific needs. Just as a hint print logical tree of nodes, like below, and then manipulate the results if ever needed.
So the tree would look like this for example (Note I added one more depth level)
and the code to reproduce
Private i As Long
Private depth As Long
Sub Main()
Cells.ClearContents
Dim dict As New Dictionary
Dim subDict As New Dictionary
Dim lvlDict As New Dictionary
lvlDict.Add "LVL KEY", "LVL ITEM"
subDict.Add "HELLO", ":)"
subDict.Add "WORLD", ":("
subDict.Add "OTHER", lvlDict
dict.Add "FOO", "BAR"
dict.Add "BOO", subDict
i = 1
depth = 0
TraverseDictionary dict
Columns.AutoFit
End Sub
Private Sub TraverseDictionary(d As Dictionary)
For Each Key In d.Keys
Range("A" & i).Offset(0, depth) = "KEY: " & Key
If VarType(d(Key)) = 9 Then
depth = depth + 1
TraverseDictionary d(Key)
Else
Range("B" & i).Offset(0, depth) = "ITEM: " & d(Key)
End If
i = i + 1
Next
End Sub
and spreadsheet result:
To get the variable type or its type name you can use this:
Debug.Print TypeName(dict("HELLO")), VarType(dict("HELLO"))