Iterate ArrayList in for loop - vba

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

Related

UBound Array Definition

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

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

VBA UBound Function

I'm trying to explore UBound applications for my code in Visual Basic for Applications (VBA). Let's say I have a 4 by 2 array...(A1:B4) and I want to count the number of rows. I would think my code would be...
Function test(list) As Double
test = UBound(list)
End Function
My input is =test(A1:B4)but so far I get "#value!" error. I thought the return would be 4.
What am I doing wrong? I know how to get the number of rows using the row command but I simply want to go through the coding exercise.
List is range object not an array. Range.Value will return an array of values from a range and Range.Formula will return an array of formulas from a range.
Function test(list As Range) As Double
test = UBound(list.Value)
End Function
It seems that you have 2-dimensional array, therefore you have to provide additional parameter for UBound like:
UBound(array, dimension)
Please remember, if you get array from Excel Range than it is 1-based array.
Complete solution can look like this one:
Function testArrray(List, Dimmension)
Dim ListConverted As Variant
ListConverted = List
testArrray = UBound(ListConverted, Dimmension)
End Function
Sample call: =testArrray(G15:H20,1) produces 6 as a result which is correct.
Since you do not Dim list explicitly, it is Variant. So if you call =TEST(A1:B4) it will be a Range and not an Array. A Range does not have a UBound but does have Rows.Count.
So:
Function test(list As Range) As Double
test = list.Rows.Count
End Function
will work.
Or if you really need an Array, you could do:
Function test(list As Variant) As Double
list = list
test = UBound(list)
End Function
The list = list does the following: If list is a Range-Object then it will be implicit converted to Array since Set is not used to set an object. If list is an Array already, then it will be also an Array after that.
This is how you get the number of rows in a function.
Option Explicit
Function l_number_of_rows(rng_range As Range) As Long
l_number_of_rows = rng_range.Rows.Count
End Function
if you want the one from the dimension, this is a possible solution.
Sub test()
Dim MyArray(1 To 2, 0 To 3) As Long
MyArray(1, 0) = 10
MyArray(1, 1) = 11
MyArray(1, 2) = 12
MyArray(1, 3) = 13
MyArray(2, 0) = 20
MyArray(2, 1) = 21
MyArray(2, 2) = 22
MyArray(2, 3) = 23
Debug.Print MyArray(UBound(MyArray), 3)
End Sub
The UBound function is to give you elements in a variant array, you're passing it a range. You could try this:
Sub mains()
Dim myRange As Range, myArray As Variant
Set myRange = Range("A1:B4")
myArray = myRange.Value
Debug.Print test(myArray)
End Sub
Function test(list) As Double
test = UBound(list)
End Function
EDIT With a two dimensional range (as mentioned by KazimierzJawor) the default ubound will be the vertical, as you wanted, if you want to specify, you can add the optional perameter UBound(list, 1), but with the data you specified this defults to 4 as you wished
Ubound is a function that works on arrays you are passing it a range
try this
dim i(3)
i(0) = 1
i(1) =2
i(2) = 3
msgbox ubound(i)

Regarding goal-seek or fsolve (matlab) alike function in VBA

I have been struggling for a while to try and implement a goal-seek function in VBA, but with no results, which is why I am turning to you guys.
I have a Variant of double-values, where the last element is just a temporary set value, which would have to be changed (as in goalseek) so that the sum of the entire array equals to 1. Of course this is exactly what you would do in a worksheet, but I need it in VBA, handling NO cells...
Is there a way to call the Goalseek function in VBA without having to use a worksheet (or cells), and instead just working with variables?
Thanks,
Niklas
If it's only about filling the last array item then this code might come in handy:
Sub FillLastElementWithDifferenceToOne()
Dim arr As Variant
Dim i As Integer, sum As Double
ReDim arr(1 To 10)
'Fill Array with .01 - .09
For i = LBound(arr) To UBound(arr)
arr(i) = i / 100
Next i
'Calculate sum = .45
For i = LBound(arr) To UBound(arr) - 1
sum = sum + arr(i)
Next i
'Set last item of array
arr(UBound(arr)) = 1 - sum
'Clean up
Set arr = Nothing
End Sub
Otherwise please specify what exactly you want to accomplish with VBA and where the problems are and post the code where the problems occur.

VBA: Don't go into loop when array is empty

I have a loop that can look like this:
For Each article In artAll
Next
or like this:
For i = 0 To Ubound(artAll)
Next
When the array length is 0, I get an error message. What is a good way to skip the loop when the array is empty? I suspect that I should use
On Error Goto
but I need help finalizing a solution.
If Len(Join(artAll, "")) = 0 Then
'your for loops here
Should work
I use this function to test for empty arrays:
Public Function isArrayEmpty(parArray As Variant) As Boolean
'Returns false if not an array or dynamic array that has not been initialised (ReDim) or has been erased (Erase)
If IsArray(parArray) = False Then isArrayEmpty = True
On Error Resume Next
If UBound(parArray) < LBound(parArray) Then isArrayEmpty = True: Exit Function Else: isArrayEmpty = False
End Function
Then in your main code:
If isArrayEmpty(yourArray) Then
'do something - typically:
MsgBox "Empty Array"
Exit Function
End If
For i = LBound(yourArray,1) To UBound(yourArray,1)
'do something
Next i
I like the solution given by #Dan but thought I would throw out there how I would normally handle an undimensionalized array:
Dim lngUboundTest As Long
lngUboundTest = -1
On Error Resume Next
lngUboundTest = UBound(artAll)
On Error GoTo 0
If lngUboundTest >= 0 Then
'Your loop...
This is an old question, but I found this solution to the problem, and it could be helpful to others:
If (Not myArray) = True Then
'Undimensionalized array. Respond as needed.
Else
'Array isn't empty, you can run your loop.
End If
It helped my out in a recent project, and found it to be very handy.
I found this thread looking for a solution to a problem where looping through a multidimensional array would fail if a dimensioned element was empty. I created the array by looping through a source that could have up to 6 datasets. Then after processing I would repeat this 19 more times.
Dim varDeskData As Variant
Dim varDesk As Variant
ReDim varDesk(1 To 6)
For y = 1 To 6
ReDim varDeskData(1 To 4)
varDeskData(1) = "nifty integer from source(y)"
varDeskData(2) = "nifty string from source(y)"
varDeskData(3) = "another nifty string from source(y)"
varDeskData(4) = "another nifty string from source(y)"
varDesk(y) = varDeskData
Next y
When I ran the following, I would get the first three processed but then it would fail on the fourth, because I had only loaded three into the parent array:
For y = 1 To 6
If varDesk(y)(1) > 0 Then
... do nifty stuff ...
End If
End If
Using the IsEmpty procedure on the top level elements of the parent array fixed this:
For y = 1 To 6
If IsEmpty(varDesk(y)) = False Then
If varDesk(y)(1) > 0 Then
... do nifty stuff ...
End If
End If
End If