If I set an array as dynamic, I get a subscript out of range error. If I set it as static, I get an array already dimentioned error. What am I missing?
Dim arrTrips() As String 'dynamic
Dim arrTrips(1 To 99) As String 'static
...
For i = 2 To lastrow
If .Cells(i, "C").Value2 = Target.Value2 Then
ReDim Preserve arrTrips(UBound(arrTrips) + 1) <-- error here
arrTrips(UBound(arrTrips)) = .Cells(i, "M").Value2
Debug.Print arrTrips(UBound(arrTrips))
End If
Next
Edit: adding more context. I am adding items to the array inside a loop.
LOL I fixed it. It's so stupid. All I did was add ReDim arrTrips(1) As String right below Dim arrTrips() As String
As mentioned, you're intializing an empty array, the Ubound function fails on that. There is no built-in method to test whether an array is "empty" so you need to either use error-trapping or a UDF that encapsulates error-trapping in order to determine whether the array has no dimensions.
Alternatively, since you know at the onset that the array is empty, you can simply ReDim it to something, and then you can later ReDim Preserve it within your loop.
Sub staticArra()
Dim arrTrips() As String
ReDim arrTrips(1 To 1) As String
Dim i As Integer
For i = 1 To 10
ReDim Preserve arrTrips(LBound(arrTrips) To UBound(arrTrips) + 1) As String
Next
End Sub
The second one, using ReDim arrTrips(1 to 99) fails to compile, by design.
https://learn.microsoft.com/en-us/office/vba/Language/Reference/user-interface-help/redim-statement
The ReDim statement is used to size or resize a dynamic array that has already been formally declared by using a Private, Public, or Dim statement with empty parentheses (without dimension subscripts).
But the ideal approach (in terms of conserving lines & avoiding redundant calls) would be to instantiate your array using ReDim instead of Dim, with a subscript. You can ReDim Preserve this:
Sub f()
ReDim arrTrips(0) As String
Dim i As Integer
For i = 1 To 10
If (i - 1) > UBound(arrTrips) Then
ReDim Preserve arrTrips(i)
End If
Next
End Sub
In short: give your array an upper bound to begin with.
Declaration 1
Dim arrTrips() As String
This is dynamic but its empty. When you do a Ubound on this, compiler will not find a size and will throw an error. Below will work since it is not derived from your arrTrips array
ReDim Preserve arrTrips(10)
Declaration 2:
Dim arrTrips(1 To 99) As String
This being static array, it can only be dimensioned once and will throw an error if you try to redimesion it.
You can use a counter:
Dim arrTrips() As String
...
Dim k As Long
k = 1
For i = 2 To lastrow
If .Cells(i, "C").Value2 = Target.Value2 Then
ReDim Preserve arrTrips(1 to k)
arrTrips(k) = .Cells(i, "M").Value2
Debug.Print arrTrips(k)
k = k + 1
End If
Next
You can also use COUNTIF to set the size of the array before the loop:
Dim arrTrips() As String
...
Dim k As Long
k = Application.CountIf(.Range("C:C"), Target.Value2)
ReDim arrTrips(1 To k)
Dim j As Long
j = 1
For i = 2 To lastrow
If .Cells(i, "C").Value2 = Target.Value2 Then
arrTrips(j) = .Cells(i, "M").Value2
Debug.Print arrTrips(j)
j = j + 1
End If
Next
Related
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 ...
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.
I have a column that contains mixed strings and I need to find all the unique strings and declare to either a string or array variable. The last row of the column will vary so I cannot use a definite range. I was thinking of using some form of string comparison of the preceding cell and current cell, but like I said the data is mixed so when redundant data comes up this complicates the problem. Here is a picture to try and explain it better.
EDIT: The string concatenation I will worry about that later.
Using Excel 365.
With data in A1 through A11, in another cell enter:
=UNIQUE(A1:A11)
to get:
or:
=TEXTJOIN(",",TRUE,UNIQUE(A1:A11))
to get a comma-separated list:
EDIT#1:
With VBA, try this UDF:
Public Function unikue(rng As Range)
Dim arr, c As Collection, r As Range
Dim nCall As Long, nColl As Long
Dim i As Long
Set c = New Collection
nCall = Application.Caller.Count
On Error Resume Next
For Each r In rng
c.Add r.Text, CStr(r.Text)
Next r
On Error GoTo 0
nColl = c.Count
If nCall > nColl Then
ReDim arr(1 To nCall, 1 To 1)
For i = 1 To nCall
arr(i, 1) = ""
Next i
Else
ReDim arr(1 To nColl, 1 To 1)
End If
For i = 1 To nColl
arr(i, 1) = c.Item(i)
Next i
unikue = arr
End Function
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
I'm setting up a subroutine to perform matches between two worksheets. The arrays are one dimensional going from the first cell of data to the last, which is held within a variable.
The data in the arrays are not numerical, but if I ReDim them as strings I get a type mismatch in the initialization.
SheetOneLastRow and SheetTwoLastRow are subroutines which find the last row in each sheet to be held in the variables FirstLastRow and SecondLastRow which are declared globally because they are used in other subs.
EDIT 1: The error is on the line:
If search(i) = arr(j) Then
Value of FirstLastRow is 9589 and SecondLastRow is 20750.
The search and arr have only been declared here with ReDim.
Sub Match()
SheetOneLastRow
SheetTwoLastRow
Dim i, j As Integer
ReDim arr(SecondLastRow - 2) As Variant
ReDim search(FirstLastRow - 2) As Variant
search = Range(wksv.Cells(2, 11), wksv.Cells(FirstLastRow, 11))
arr = Range(wkst.Cells(2, 6), wkst.Cells(SecondLastRow, 6))
For i = 2 To FirstLastRow
For j = 2 To SecondLastRow
If search(i-2) = arr(j-2) Then
wkst.Cells(j, 3) = wksv.Cells(i, 3)
End If
Next j
Next i
End Sub
Search() is a 2D array, and the code is using it as a 1D array.
In general, passing range to arrays is not complicated, but there are a few tricks, you should be aware of. First trick - whenever the range is passed like this:
search = Range(wksv.Cells(2, 11), wksv.Cells(FirstLastRow, 11)) it is passed to a 2-dimensional array. See the blue highlighted line at the screenshot:
The problem with the 2-dimensional arrays is that they are of two dimensions. E.g., you should be looking for Search(2,1) instead of Search(2). Or in the code above it should be: If Search(i,1) = arr(j,1) Then
There are probably better ways to solve the problem, e.g. passing the range to a single dimensional array, as in the example here - https://stackoverflow.com/a/52467171/5448626
This is what would happen, if you force the range to be a 1D array:
Sub Match()
Dim i, j As Integer
FirstLastRow = 9589
SecondLastRow = 20750
ReDim arr(SecondLastRow - 2) As Variant
ReDim Search(FirstLastRow - 2) As Variant
With Worksheets(1) 'put wksv
Search = Application.Transpose(.Range(.Cells(2, 11), .Cells(FirstLastRow, 11)))
End With
With Worksheets(2) 'put wkst
arr = Application.Transpose(.Range(.Cells(2, 6), .Cells(SecondLastRow, 6)))
End With
For i = 2 To FirstLastRow - 2 '-2 is needed because of ReDim arr(SecondLastRow - 2)
For j = 2 To SecondLastRow - 2
If Search(i) = arr(j) Then
Worksheets(1).Cells(j, 3) = Worksheets(2).Cells(i, 3)
End If
Next j
Next i
End Sub