UBound Array Definition - vba

Having trouble understanding how to use UBound.
If I have a dynamic column A where the rows count is constantly changing, but I want to grab the upper bound to define in a for loop - how would I go about doing so? I keep getting a runtime error with the below method.
arr = Worksheets("Sheet1").Range("A1").CurrentRegion.Value
For i = 1 to UBound(arr, 1)
etc.
Next i

When setting an array equal to a range value, it automatically sets it as a two dimensional array, even if the range is only one column. Look at the example below:
Private Sub workingWithArrayBounds()
Dim Arr As Variant
Dim RowIndex As Long
Dim ColumnIndex As Long
Arr = Worksheets("Sheet1").Range("A1").CurrentRegion.Value
'VBA FUNCTION FOR CHECKING IF SOMETHING IS AN ARRAY
If IsArray(Arr) Then
'FIRST LOOP GOES THROUGH THE ROWS
For RowIndex = LBound(Arr, 1) To UBound(Arr, 1)
'THE SECOND LOOP GOES THROUGH THE COLUMNS
For ColumnIndex = LBound(Arr, 2) To UBound(Arr, 2)
'NOW YOU HAVE ACCESS TO EACH ELEMENT USING THE ROWINDEX AND THE COLUMN INDEX
Debug.Print Arr(RowIndex, ColumnIndex)
Next
Next
End If
End Sub
If there is only one column of data, the bounds would look something like (1 to x, 1 to 1). So you would need to pass in the second bounds to it. To be safe, it is always recommended to use both the LBound and UBound when looping through arrays as these can vary from array to array.
Another thing to check is if the array is actually allocated. In the scenario of setting an array equal to the value of a range, you can first check if there are values actually in your range to retrieve.
An alternative is to use a function to check and see if array is empty or not. Cpearson has a good function for that as well as many other helpful functions # http://www.cpearson.com/excel/vbaarrays.htm
Public Function IsArrayEmpty(Arr As Variant) As Boolean
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' IsArrayEmpty
' This function tests whether the array is empty (unallocated). Returns TRUE or FALSE.
'
' The VBA IsArray function indicates whether a variable is an array, but it does not
' distinguish between allocated and unallocated arrays. It will return TRUE for both
' allocated and unallocated arrays. This function tests whether the array has actually
' been allocated.
'
' This function is really the reverse of IsArrayAllocated.
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Dim LB As Long
Dim UB As Long
Err.Clear
On Error Resume Next
If IsArray(Arr) = False Then
' we weren't passed an array, return True
IsArrayEmpty = True
End If
' Attempt to get the UBound of the array. If the array is
' unallocated, an error will occur.
UB = UBound(Arr, 1)
If (Err.Number <> 0) Then
IsArrayEmpty = True
Else
''''''''''''''''''''''''''''''''''''''''''
' On rare occassion, under circumstances I
' cannot reliably replictate, Err.Number
' will be 0 for an unallocated, empty array.
' On these occassions, LBound is 0 and
' UBoung is -1.
' To accomodate the weird behavior, test to
' see if LB > UB. If so, the array is not
' allocated.
''''''''''''''''''''''''''''''''''''''''''
Err.Clear
LB = LBound(Arr)
If LB > UB Then
IsArrayEmpty = True
Else
IsArrayEmpty = False
End If
End If
End Function

Related

In VBA, how to check if two arrays contain the same element?

I suspect I might need a function for that. I would like to return a Boolean. E.g., firstarray = ("haha", "ya", "test") and secondarray = ("haha", "no", "stop"), then return True. Thanks
Are There Any Matching Elements in Two Arrays? (One-Liner)
A safer yet less efficient way would probably be to loop through each element in one array and, in another (inner) loop, compare it to each element of the other array exiting the loops when a match is found.
Option Explicit
Sub Test()
Dim FirstArray() As Variant
FirstArray = Array("A", "ya", CVErr(xlErrNA), "test")
Dim SecondArray() As Variant
SecondArray = Array("sa", "aha", CVErr(xlErrNA), "stop")
If FoundMatchingElement(FirstArray, SecondArray) Then
Debug.Print "True"
Else
Debug.Print "False"
End If
' Result:
' False ' ... since error values are excluded from the comparison
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns a boolean indicating whether two arrays ('Arr1', 'Arr2')
' have a same element.
' Remarks: 'Application.Match' is case-insensitive i.e. 'A = a'.
' Error values will not be considered.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function FoundMatchingElement( _
Arr1() As Variant, _
Arr2() As Variant) _
As Boolean
FoundMatchingElement _
= Application.Count(Application.Match(Arr1, Arr2, 0)) > 0
End Function
Please, use the next function. It does what you want without any iteration. It returns True if any of their elements are common to both of them:
Function MatchArrayElement(arr1, arr2) As Boolean
Dim arrMtch: arrMtch = Application.IfError(Application.match(arr1, arr2, 0), "x") 'place "x" instead of error (not matching elements)
MatchArrayElement = UBound(filter(arrMtch, "x", False)) > -1
End Function
It can be used in the next way:
Sub testMatchArrEl()
Dim arr1, arr2
arr1 = Array("hah", "ya", "test")
arr2 = Array("haha", "no", "stop")
Debug.Print MatchArrayElement(arr1, arr2)
End Sub
But if you need checking if a specific element/string exists in an array, you can use the following function:
Function arrElementMatch(El, arr) As Boolean
Dim mtch: mtch = Application.match(El, arr, 0)
arrElementMatch = Not IsError(mtch)
End Function
It can be checked in the next way:
Debug.Print arrElementMatch("haha", arr1), arrElementMatch("hah", arr1)
To check if the element exists in both array, try using:
Debug.Print arrElementMatch("haha", arr1) And arrElementMatch("haha", arr2)
Please, send some feedback after using it.
If something not clear enough, do not hesitate to ask for clarifications...

Iterate ArrayList in for loop

I have a function that makes of a ArrayList in vba. I try to iterate through it, using for loop, but I get an error. I'm not sure what use as a LBound and UBound and I can't use either of those functions on the ArrayList.
Function test(dataArray() As Double)
Dim i As Long
Dim arr As Object
Set arr = CreateObject("System.Collections.ArrayList")
For i = LBound(dataArray) To UBound(dataArray)
arr.Add dataArray(i)
Next
arr.Sort
For i = 1 To arr.Count
If arr(i) <= 10000 Then 'error
...
End If
Next
End Function
I get a index out of range, index must be positive and should not exceed the collection's size error inside the cycle. How would I iterate through the ArrayList without errors inside for cycle?
collection starts at 0 not at 1. Here is a correction
For i = 0 To arr.Count-1
If arr(i) <= 10000 Then 'error
...
End If
Next

Converting VBA Collection to Array

I am trying to create an array with all the worksheet names in my workbook that have the word 'Template' in it. I thought the easiest way to do this would be to create a collection first and then convert it to an array but I am having trouble. Right now the error I am getting is on the
collectionToArray (col)
line. I am receiving an
Argument Not Optional error
Pretty stuck, any help is super appreciated. Thanks!!
Public col As New Collection
Public Sub Test()
For Each ws In ThisWorkbook.Worksheets
If InStr(ws.Name, "Template") <> 0 Then
col.Add ws.Name
End If
Next ws
collectionToArray (col)
End Sub
Function collectionToArray(c As Collection) As Variant()
Dim a() As Variant: ReDim a(0 To c.Count - 1)
Dim i As Integer
For i = 1 To c.Count
a(i - 1) = c.Item(i)
Next
collectionToArray = a
End Function
collectionToArray (col)
Notice that whitespace between the function's name and its argument list? That's the VBE telling you this:
I'll take that argument, evaluate it as a value, then pass it ByVal to that procedure you're calling, even if the signature for that procedure says ByRef, explicitly or not.
This "extraneous parentheses" habit is inevitably going to make you bump into weird "Object Required" runtime errors at one point or another: lose it.
The Function is overdoing it IMO: a Variant can perfectly well wrap an array, so I'd change its signature to return a Variant instead of a Variant().
Integer being a 16-bit signed integer type (i.e. short in some other languages), it's probably a better idea to use a Long instead (32-bit signed integer, i.e. int in some other languages) - that way you'll avoid running into "Overflow" issues when you need to deal with more than 32,767 values (especially common if a worksheet is involved).
Public col As New Collection
This makes col an auto-instantiated object variable, and it has potentially surprising side-effects. Consider this code:
Dim c As New Collection
c.Add 42
Set c = Nothing
c.Add 42
Debug.Print c.Count
What do you expect this code to do? If you thought "error 91, because the object reference is Nothing", you've been bitten by auto-instantiation. Best avoid it, and keep declaration and assignments as separate instructions.
Other than that, CLR's answer has your solution: a Function should return a value, that the calling code should consume.
result = MyFunction(args)
You'll notice the VBE clearing any whitespace you might be tempted to add between MyFunction and (args) here: that's the VBE telling you this:
I'll take that argument, pass it to MyFunction, and assign the function's return value to result.
It's all there, you're just not using the Function as a function. You need to store the result in something, like 'NewArray'..?
Public col As New Collection
Public Sub Test()
For Each ws In ThisWorkbook.Worksheets
If InStr(ws.Name, "Template") <> 0 Then
col.Add ws.Name
End If
Next ws
' Tweaked as per Vityata's comment
If col.Count > 0 Then
newarray = collectionToArray(col)
Else
' Do something else
End If
End Sub
Function collectionToArray(c As Collection) As Variant()
Dim a() As Variant: ReDim a(0 To c.Count - 1)
Dim i As Integer
For i = 1 To c.Count
a(i - 1) = c.Item(i)
Next
collectionToArray = a
End Function
This is my collectionToArray function:
Public Function CollectionToArray(myCol As Collection) As Variant
Dim result As Variant
Dim cnt As Long
If myCol.Count = 0 Then
CollectionToArray = Array()
Exit Function
End If
ReDim result(myCol.Count - 1)
For cnt = 0 To myCol.Count - 1
result(cnt) = myCol(cnt + 1)
Next cnt
CollectionToArray = result
End Function
It is better than the one you are using, because it will not give an error, if the collection is empty.
To avoid the error on an empty collection in your case, you may consider adding a check like this:
If col.Count > 0 Then k = CollectionToArray(col)
Instead of using a standard collection, you can use a dictionary because of the .keys method that can transfer all info to an array. Less coding, simple :)
Dim ws As Worksheet
Dim arr
Dim Dic As Scripting.Dictionary
Set Dic = New Dictionary
For Each ws In ActiveWorkbook.Worksheets
If ws.Name Like "Template" Then
Dic.Add ws.Name, "Awesome"
End If
Next ws
arr = Dic.Keys

Run-time error '9': Subscript out of range with Dynamic Array

Im trying to add a value to a dynamic array but keep getting a run-time error with this. I've found a few different sources saying that this should be the answer, so I cant figure out what I have done wrong...
Sub addtoarray()
Dim catSheets() As String
ReDim Preserve catSheets(UBound(catSheets) + 1)
catSheets(UBound(catSheets)) = "Chemicals"
End Sub
When you create the catSheets() array, it's dimensionless. Therefore, you cannot use UBound() to determine the array's upper boundary.
You can certainly follow up a Dim () with a ReDim if you want to specify an array size, but you won't be able to query the array dimensions until after you've given it some.
So you have a few options. First, you could follow up your Dim () with an immediate ReDim to give your array an initial size:
Dim catSheets() As String
ReDim catSheets(0) As String
...
ReDim Preserve catSheets(...) As String
Or, you could just use ReDim from the start to assign an initial size and still have the ability to ReDim later:
ReDim catSheets(0) As String
...
ReDim Preserve catSheets(...) As String
Alternatively, you could use the Array() function and store it as a Variant. Done this way, you can query the UBound(). For example:
Dim catSheets As Variant
catSheets = Array()
Debug.Print UBound(catSheets) ' => -1
ReDim Preserve catSheets(UBound(catSheets) + 1)
catSheets(UBound(catSheets)) = "Chemicals" ' Works fine
What you can do is to use Variant if you want to preserve the way to use the code.
Sub addtoarray()
Dim catSheets As Variant
catSheets = Array() ' Initially Empty (0 to -1)
Redim Preserve catSheets(Ubound(catSheets) + 1) ' Now it's 0 to 0
catSheets(Ubound(catSheets)) = "Chemicals" ' index here is 0
End Sub

receiving "out of range" error, can't figure out why

Let me start by first thanking everyone for the help/intention to help. This community is phenomenal. Second: I'm pretty new at this- before this week I'd learned basic in highschool a decade ago but no other programming experience outside of theory.
Without further ado, here's my issue:
Working on code to find unique variables (I know there's a lot of opensource stuff out there, need to customize this though). When I go to populate the array with the very first string I run into an 'out of range' error at array(1), which I had explicity set (1 TO UB), with UB being the upper bound. I've also double checked the value of UB with msgbox and it's at 15 with my dummy data, so that shouldn't be an issue. I've set the values in the array to empty (have also done so with 0, to no avail).
The error occurs at "ResultArray(1) = CurrentArray(1)"
I'm at a loss; any assistance would be much appreciated.
Option Explicit
Sub unque_values()
'''''''''Variable declaration
'
' CurrentArray() is the array taken from the worksheet
' Comp is the method of comparing inputs (either case sensitive or case insensitive)
' resultarray() is the array that unique values are placed
' UB is the upper bound of Result Array
' resultindex is the variable that keeps track of which cells are unique and which are not
' n is a helped variable that assists with resizing the array
Dim currentarray() As Variant
Dim comp As VbCompareMethod
Dim resultarray() As Variant
Dim UB As Long
Dim resultindex As Long
Dim n As Long
Dim v As Variant
Dim inresults As Boolean
Dim m As Long
' set variables to default values
Let comp = vbTextCompare
Let n = 0
' count the number of cells included in currentarray and populate with values
Let n = ActiveWorkbook.Worksheets("Data").Range("A:A").Count
Let UB = ActiveWorkbook.Worksheets("Data").Range("A" & n).End(xlUp).Row
' dimension arrays
ReDim resultarray(1 To UB)
ReDim currentarray(1 To UB)
' don't forget to change to named ranges
Let currentarray() = Range("f2", "f" & UB)
' populate resultarray with empty values
For n = LBound(resultarray) To UBound(resultarray)
resultarray(n) = Empty
Next n
MsgBox (n)
'check for invalid values in array
For Each v In currentarray
If IsNull(n) = True Then
resultarray = CVErr(xlErrNull)
Exit Sub
End If
Next v
' assumes the first value is unique
resultindex = 1
'''''''''''''''''''''''''''''''''''''''''error is this line''''''''''''''
resultarray(1) = currentarray(1)
' Search for duplicates by cycling through loops
' n = index of value being checked
' m = index of value being checked against
For n = 2 To UB
Let inresults = False
For m = 1 To n
If StrComp(CStr(resultarray(m)), CStr(currentarray(n)), comp) = 0 Then
inresults = True
Exit For
End If
Next m
If inresults = False Then
resultindex = resultindex + 1
resultarray(resultindex) = currentarray(n)
End If
Next n
ReDim Preserve resultarray(1 To resultindex)
End Sub
You've assigned to currentArray a range array. These are always two-dimensional arrays.
You should be able to resolve it with:
resultarray(1) = currentarray(1, 1)
You would need to modify a few more lines in your code to refer to both dimensions of the array.
Alternatively, with the least manipulation to your existing code, transpose the array which turns it to a one-dimensional array. This should require no other changes to your code.
Let currentArray() = Application.Transpose(Range("f2", "f" & UB))
Try with ActiveWorkbook.Worksheets("Data").UsedRange.Columns(1).cells.Count