call dictionary from one sub to another in VBA - excel

I'm stuck with a problem when I'm working with a dictionary in VBA. The reason why I want to work with a dictionary and a do-while loop is because I have variables with different length, that I want to loop through.
First I want to give the dic keys and and items.
The reason why I skip one col for each loop is because each series has a col with dates and then a col with prices. If it is possible I want to capture the dates that match the prices in the same dictionary.
Sub opg1(dicSPX As Object)
Dim i As Integer, m As Integer
Dim varColLeng As Variant
Set dicSPX = CreateObject("Scripting.Dictionary")
m = 10
Sheets("Data").Activate
ReDim intCol(1 To nCol)
'opretter dictionary
ReDim n(1 To m)
Do While n <> ""
For i = 1 To mn
' redim preserve IntColLen
dicSPX.Add Cells(1, 2 + ((i - 1) * 2)).Value, Range(Cells(9, 2 + ((i - 1) * 2)), Cells(n, 2 + ((i - 1) * 2))).Value
Next i
Loop
End Sub
then I want to execute a procedure for all keys in my dic. I want to compute returns in different time series.
However, when I call the dic, to the sub Returns() I get an error (Compile error: Variable not defined). I'm new to dictionaries and I probably missed a small detail.
Sub Returns()
Call opg1(dicSPX)
Dim dicSPX As New Scripting.Dictionary
Dim varKey As Variant, varArr As Variant
For Each varKey In dicSPX
varArr = dicSPX(varKey)
For i = LBound(varArr, 1) To UBound(varArr, 1)
For j = LBound(varArr, 2) To UBound(varArr, 2)
' varReturns(i,j) = compute the return here
Next
Next
Next
Any suggestions? I hope the question is clear.
Thank you

It should be clear which variable is not defined by the line which is highlighted after the error is thrown. At a quick glance I can tell that you didn't define i or j:
Sub Returns()
Dim dicSPX As New Scripting.Dictionary
opg1(dicSPX)
Dim varKey As Variant
For Each varKey In dicSPX
Dim varArr As Variant
varArr = dicSPX(varKey)
Dim i As Long
For i = LBound(varArr, 1) To UBound(varArr, 1)
Dim j As Long
For j = LBound(varArr, 2) To UBound(varArr, 2)
varReturns(i,j) = compute the return here
Next
Next
Next
End Sub
You should always explicitly define each variable, one line at a time, right before they are used. This way the declaration is always near the usage so you can see it on the same page.
You should also make opg1 a function that returns a dictionary. That would clarify your intent. Passing a variable by reference makes it harder to tell how the program works to the reader.

Related

Application.Match doesn work - types incompatible

I have a table with strings, and I want to check whether those strings are already stored as elements in a certain array. If not, they're supposed to be added as the last element of the respective array. For some reason, I receive an error stating that the types are incompatible in the line mtch = Application.Match(srch, arr, 0).
Also, I want to work with this approach and not a different one since this is supposed to be the basis for further checks.
Sub Test_4()
Dim i, j, k As Long
Dim arr As Variant
Dim srch, mtch As String
With Worksheets("table1")
For i = 1 To .Range("A1").End(xlDown).Row
srch = .Range("A" & i).Value
mtch = Application.Match(srch, arr, 0)
If Not IsNumeric(mtch) Then
ReDim Preserve arr(UBound(arr) + 1)
arr(UBound(arr)) = mtch
End If
Next i
End With
End Sub
Your base fault is - as Mate wrote - that arr isn't initialized in the first run
You can use this code - it uses the VBA Filter function to test wether a value is already part of an array or not.
Public Function getUniqueValuesFromRange(rg As Range) As Variant
Dim arrResult As Variant
ReDim arrResult(0 To rg.Cells.Count - 1) 'dim arrResult to the max
Dim iCell As Long, iResult As Long
Dim value As Variant
For iCell = 1 To rg.Cells.Count
value = rg.Cells(iCell)
If UBound(Filter(arrResult, value)) = -1 Then 'value is not part of arrResult
arrResult(iResult) = value
iResult = iResult + 1
End If
Next
'it is "cheaper" to redim the array once at the end of the function
ReDim Preserve arrResult(iResult - 1)
getUniqueValuesFromRange = arrResult
End Function
You can call this function like this:
arr = getUniqueValuesFromRange(Worksheets("table1").UsedRange.Columns("A"))
EDIT: you can use
If Not IsNumeric(Application.Match(value, arrResult, 0)) Then
instead of
If UBound(Filter(arrResult, value)) = -1 Then
If you have Excel 365 you can use the UNIQUE function as well
Public Function getUniqueValuesFromRange(rg As Range) As Variant
With Application.WorksheetFunction
getUniqueValuesFromRange= .Transpose(.Unique(rg))
End With
End Function
Be aware: there is no check, that you pass only one column ...

User defined type variable

I am trying to write 3 user defined type variables which need to be associated with each other like this;
Type tdrivers
Strfirstname as string
Strsurname as string
Intage as integer
End type
Type Tcars
Strmake as string
Strmodel as string
Lngcc as long
Driverid() as tdrivers
End type
Type T_Race
Strlocation as string
DteRacedate as date
IntYear as integer
CarsID() as Tcars
End Type
Sub CreateRace()
Dim myrace() as T_Race
'Variables to hold integer 'values at runtime
Dim A as integer
Dim B as integer
Dim C as integer
'this line redims myrace ok
Redim myrace(A)
'This line doesn't do anything
'When I try to redim the 'carsID() array nested inside 'the myrace(A) like so;
Redim myrace(A).carsID(B)
'This line obviously does 'nothing either
Redim myrace(A).CarsID(B).driverid(C)
I need to be able to assign races to the myrace() array and then assign cars to each race they have taken part in and then drivers to cars they have driven. So the carsID() must be nested within myrace() and driverid() nested within carsID()
Once I know how to redim carsID() in can then redim Driverid() which is nested further within.
If I make all the arrays fixed with a constant value such as 8 then the sub runs ok and all races, cars and drivers are nested correctly. Its the redim on nested dynamic arrays that is failing. Hope this makes sense. Can anyone help. Thanks
The point is that you have to ReDim every sub-array individually. The following example initializes all sub arrays and prints them at the end:
Sub Example()
Dim i As Integer
Dim j As Integer
Dim k As Integer
ReDim myRace(5)
For i = 1 To 5
ReDim myRace(i).CarsID(5)
For j = 1 To 5
ReDim myRace(i).CarsID(j).Driverid(5)
For k = 1 To 5
myRace(i).CarsID(j).Driverid(k).Strfirstname = Chr(k + Asc("a")) & Str(i) & Str(j) & Str(k)
Next k
Next j
Next i
' Now print it
For i = 1 To 5
For j = 1 To 5
For k = 1 To 5
Debug.Print myRace(i).CarsID(j).Driverid(k).Strfirstname
Next k
Next j
Next i
End Sub

Visual Basic query (excel)

I just started a new job and am trying to use VBA within excel to store values within a multidimensional array.
so I first want to search through a column for values and as they occur, i want to store them once, and assign a product number that will increment each time it encounters a new unique String value in the column. then just print the number to the cell and only change it as it increments/
the values will be Strings, (product styles). the product number will be a number.(int)
will it be possible to do this in VBA for excel. I read online you can't use different data types in the same array.
sorry new to VBA and any help would be great. if i would be better off to use a normal function in excel let me know.
You can declare you array as Variant and then you can store different types in the Array. For example
Sub test()
Dim arr() As Variant
Dim i As Long
ReDim arr(0 To 1)
arr(0) = "hi"
arr(1) = 1
For i = LBound(arr, 1) To UBound(arr, 1)
Debug.Print arr(i)
Debug.Print VarType(arr(i))
Next
End Sub
This prints in the immediate window the following
hi
8 ' This corresponds to the type String
1
2 ' This corresponds to the type Integer
EDIT
As per the question, yes you can have different types of arrays as entries in an Variant-Array. For example:
Sub test2()
Dim arr As Variant
Dim str_a(3) As String
Dim lng_a(8) As Long
Dim i As Long, j As Long
For i = LBound(str_a, 1) To UBound(str_a, 1)
str_a(i) = "hi " & i
Next
For i = LBound(lng_a, 1) To UBound(lng_a, 1)
lng_a(i) = i
Next
ReDim arr(0 To 1)
arr(0) = str_a
arr(1) = lng_a
For i = LBound(arr, 1) To UBound(arr, 1)
Debug.Print VarType(arr(i))
For j = LBound(arr(i), 1) To UBound(arr(i), 1)
'Do Stuff with the arrays
Next
Next
End Sub
This prints
8200
8195
An array always has the VarType 8192 + the value of type. For example String has the Value of 8, therefore an array of Type String has 8200 (8192+8).

Array() = range().value

I saw array() = range().value in an example and I'm trying to understand how it works.
Sub test()
Dim arr() As Variant
arr() = Range("E5:E7").Value
For i = 0 To UBound(arr)
Debug.Print arr(i)
Next i
End Sub
First, above code is giving me subscript out of range error. How come ? Second, what part of the documentation would let me know how array() = range().value would play out without testing it ? My hypothesis is that it will go through the cells from the top left to the bottom right and add them to the array. How can I confirm that though ?
I see two issues with your code. The first is that you start i at 0, but arrays in Excel begin at index 1. Instead of For i = 0 you can use For i = LBound(arr) like you use UBound(arr) or just start it at 1.
Second, and more importantly, an array of cells has both columns and rows. When you read a range into a variant array, you get a two-dimensional result (rows and columns) and not a one-dimensional result as you seem to be expecting.
Try this:
Sub test()
Dim arr() As Variant
Dim i As Long, j As Long
arr() = Range("E5:E7").Value
For i = 1 To UBound(arr, 1)
For j = 1 To UBound(arr, 2)
Debug.Print arr(i, j)
Next j
Next i
End Sub
If you want to get just the values of the cells into a one dimensional array, you can do that by using the Transpose function, like this:
arr() = Application.WorksheetFunction.Transpose(Range("E5:E7").Value)
If you do this, the array is now one-dimensional and you can iterate through it like you were trying to.
arr() = Application.WorksheetFunction.Transpose(Range("E5:E7").Value)
For i = 1 To UBound(arr)
Debug.Print arr(i)
Next i
This is a good read for you: http://www.cpearson.com/excel/ArraysAndRanges.aspx
The reason you're getting "out of range" is because it returns a 2 dimensional array.
Your line of code For i = 0 To UBound(arr) should be For i = 1 To UBound(arr,1)
Also, the array starts at 1, so don't use the 0 For i = 1 to UBound(arr, 1)
Your corrected code would be:
Sub Test()
Dim arr() as Variant
arr = Range("E5:E7")
For i = 1 To UBound(arr, 1)
MsgBox (arr(i, 1))
Next i
End Sub
It's basically loading the cell values of E5 - E7 into an array. But it is going to be two dimensional. So you will need Debug.Print arr(i, 1)
Sub test()
Dim arr() As Variant
arr() = Range("E5:E7").Value
For i = 1 To UBound(arr)
Debug.Print arr(i, 1)
Next i
End Sub

Split data string over columns AND rows using VBA

I am trying to speed up a currently working automated workbook.
PHP sends a string similar to the below to VBA:
1[|:#:|]text-one[|:#:|]code-one[|:#:|]qty-one[{:#:}]
2[|:#:|]text-two[|:#:|]code-two[|:#:|]qty-two[{:#:}]
where
[|:#|] represents "new column"
[{:#:}] represents "new row"
When it is parsed by the VBA this is the output:
I currently use the following VBA code to parse this into a workbook:
myArray = Split(myReply, "[{:#:}]")
myRow = 1
For Each element In myArray
myRow = myRow + 1
subArray = Split(element, "[|:#:|]")
myCol = 2
For Each subelement In subArray
myCol = myCol + 1
Cells(myRow, myCol).Value = subelement
Next subelement
Next element
I am about to start optimising the code in this workbook and I am aware I can do something like (pseudo code):
for each element....
Range("C2:F2").Value = Split(element, "[|:#:|]") 'Example row number would be incremental
However is there a way to do it so that I can split into the entire Range?
For example, If I know there are 29 "rows" within the data that has been returned, I would like to be able to use split to place the data into all the rows.
I imagine the syntax would be something similar to the below, however this doesn't seem to work:
Range("C2:F29").Value = Split(Split(element, "[|:#:|]"),"[{:#:}]")
The optimal thing to do is to do everything in native VBA code and not interact with the Excel sheet until the end. Writing to sheet is a time consuming operation, so this procedure does it once and once only, writing the whole two-dimensional array at once, rather than writing it line by line. Therefore no need to disable screen updating, calculation, or anything else.
Function phpStringTo2DArray(ByVal phpString As String) As Variant
Dim iRow As Long
Dim iCol As Long
Dim nCol As Long
Dim nRow As Long
Dim nColMax As Long
Dim lines() As String
Dim splitLines() As Variant
Dim elements() As String
lines = Split(phpString, "[{:#:}]")
nRow = UBound(lines) - LBound(lines) + 1
ReDim splitLines(1 To nRow)
For iRow = 1 To nRow
splitLines(iRow) = Split(lines(iRow - 1), "[|:#:|]")
nCol = UBound(splitLines(iRow)) - LBound(splitLines(iRow)) + 1
' in case rows have different number of columns:
If nCol > nColMax Then nColMax = nCol
Next iRow
Erase lines
'We now have a (Variant) array of arrays. Convert this to a regular 2D array.
ReDim elements(1 To nRow, 1 To nColMax)
For iRow = 1 To nRow
nCol = UBound(splitLines(iRow)) - LBound(splitLines(iRow)) + 1
For iCol = 1 To nCol
elements(iRow, iCol) = splitLines(iRow)(iCol - 1)
Next iCol
Next iRow
Erase splitLines
phpStringTo2DArray = elements
End Function
Example usage:
Dim s As String
Dim v As Variant
s = "1[|:#:|]text-one[|:#:|]code-one[|:#:|]qty-one[{:#:}]2[|:#:|]text-two[|:#:|]code-two[|:#:|]qty-two[{:#:}]"
v = phpStringTo2DArray(s)
'Write to sheet
Range("A1").Resize(UBound(v, 1), UBound(v, 2)) = v
If you want to ignore the final line break [{:#:}], could add this line at the top of the function:
If Right(phpString, 7) = "[{:#:}]" Then phpString = Left(phpString, Len(phpString) - 7)
This wasn't as easy as I originally thought. I can get rid of one loop easily. But there's still an if test, so it doesn't break on empty strings etc. I feel a guru could make this even more efficient.
My worry is that for you this process is taking a lot of time. If you are trying to speed things up, your code doesn't look too horribly inefficient.
More likely if it's running slowly, is that the application.calculation & application.screenUpdating settings are set incorrectly.
Sub takePHP(myString As String)
'This sub takes specially formatted strings from a PHP script,
'and parses into rows and columns
Dim myRows As Variant
Dim myCols As Variant
Dim subRow As Variant
Application.ScreenUpdating = False
Application.Calculation = xlCalculateManual
myRows = Split(myString, "[{:#:}]")
x = 1
For Each subRow In myRows
bob = Split(subRow, "[|:#:|]")
If UBound(bob) <> -1 Then
Range(Cells(x, 1), Cells(x, UBound(bob) + 1)).Value = bob
x = x + 1
End If
Next
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub

Resources