I can't seem to find this problem addressed anywhere.
I need to declare a bunch of dynamic arrays as follow:
Dim list1 () as variant
Dim list2() as variant
Dim list3() as variant
...
Dim listN() as Variant
Each list is a one-dimensional dynamic array. However, I wouldn't know what "N" will be during the program. I want to make these "N" lists dynamic as well. I have tried two-dimensional dynamic arrays. But the "redim" statement requires both dimensions to be declared at the same time. In particular, I do this:
Dim BigList() as variant
...
Redim BigList(listNum, listLength)
To access/pass into a sub "list1", "list2" , "list3"..., calling "BigList(1)", "BigList(2)" gives me error. In particular, somewhere in my code, there is this portion:
sub ProcessList(byref listToProcess() as variant)
...
end sub
sub main()
...
call ProcessList(list1)
call ProcessList(list2)
...
call ProcessList(listN)
end sub
Now I can do a loop:
for i = 1 to N
Call ProcessList(list"i")
next i
This requires list"i" to be a one-dimensional dynamic array. So, after a redim BigList(listNum,listLength) and I do this:
for i = 1 to N
Call ProcessList(BigList(i)) 'i refers to listNum
next i
This gives me error "Incompatible type".
Here is one example of creating a Dictionary which is keyed to integer values (i.e., the N) and each value is initially an empty array.
You can then use something like the ExtendList function to resize those empty arrays as needed.
Sub foo()
Dim BigList As Object
Dim N As Long
Dim v as Variant
'Create an empty dictionary object
Set BigList = CreateObject("Scripting.Dictionary")
'Add N empty array to the dictionary:
N = 3
For i = 1 To N
BigList(i) = Array()
Next
'Resize one of the items in your BigList
BigList(2) = ExtendList(BigList(2), 1, 10)
v = BigList(2) 'Here you can examine v in the Locals window and see it is an array, of dimensions 1 x 10
End Sub
Function ExtendList(lst, a As Long, b As Long)
ReDim lst(a, b)
ExtendList = lst
End Function
On review of your edited question, I think you merely misunderstood how the ReDim statement works:
Redim BigList(listNum, listLength)
This re-dimensions the BigList based on the parameters listNum and listLength. It does not (as it seems you may have expected) create a list of arrays within BigList.
I think this might also work (untested, and remember arrays are zero-index):
ReDim Preserve BigList(listNum)
BigList(listNum) = Array()
ReDim BigList(listNum)(listSize)
Related
Sub Test4()
Public Function GetLength(a As Variant) As Integer
If IsEmpty(a) Then
GetLength = 0
Else
GetLength = UBound(a) - LBound(a) + 1
End If
End Function
Essentially trying to find the length of two arrays, and am a little confused on how to use this code to do so. Unsure of what to place where and how to make it work without errors. Any help would be appreciated!
Are you confused because you need to seperate the function from your sub? You should call that function something like:
Sub Test4()
Dim arr As Variant
arr = Array(1, 2, 3)
If IsArray(arr) Then Debug.Print GetLength(arr)
End Sub
Public Function GetLength(a As Variant) As Long
GetLength = UBound(a) - LBound(a) + 1
End Function
I left out IsEmpty if you are to always initialize arr, but put it back if you want to. Just in general, if you are confused on how to call functions and return their values. Have a look at the MS Docs.
The Parameter a represents your Array. The function will return the length of the first dimension of the array it receives as an Argument:
Sub TestIt()
Dim MyArray As Variant
MyArray = Split("A,B,C,D,E,F", ",") 'create an Array of Length 6
MsgBox GetLength(MyArray) 'Output the length of the array, "6"
End Sub
As noted, this may give unexpected results for a 2D array:
[[A][B][C]
[D][E][F]]
This is a 2-by-3 array, so GetLength will tell you that the length is 2. However, the number of elements in the array is 6
(Note - if this is being used in VBA, then there is no advantage to using Integer instead of Long, and I would recommend you change that line - especially since UBound and LBound both return Longs. With too large an array, you could get an Overflow error!)
Is it possible to create multi dimensional array with different element types (string and integer)?
I tried like this but wan't work
BT = Range("A12")
ReDim IT(BT) As String
ReDim RBT(BT) As Integer
ReDim IT_RBT(IT, RBT) as ???? how to create multi dim array with different variables type
Range("B2").Select
i = 0
Do
i = i + 1
IT(i) = ActiveCell
RBT(i) = i
IT_RBT(i, i) = ???? how to enter values in such array ????
ActiveCell.Offset(1, 0).Select
Loop While ActiveCell <> ""
Thank you
Use a Variant array.
Dim values() As Variant
Now, your code is making assumptions that should be removed.
BT = Range("A12") '<~ implicit: ActiveSheet.Range("A12").Value
If you mean to pull the value of A12 from a particular specific worksheet, then you should qualify that Range member call with a proper Worksheet object. See CodeName: Sheet1 for more info, but long story short if that sheet is in ThisWorkbook you can do this:
BT = Sheet1.Range("A12").Value
And now assumptions are gone. Right? Wrong. BT isn't declared (at least not here). If it's declared and it's not a Variant, then there's a potential type mismatch error with that assignment. In fact, the only data type that can accept any cell value, is Variant:
Dim BT As Variant
BT = Sheet1.Range("A12").Value
Here, we're assuming BT is a numeric value:
ReDim IT(BT) As String
That's another assumption. We don't know that BT is numeric. We don't even know that it's a value that can be coerced into a numeric data type: we should bail out if that's not the case:
If Not IsNumeric(BT) Then
MsgBox "Cell A12 contains a non-numeric value; please fix & try again."
Exit Sub
End If
ReDim IT(BT) As String
Now that will work... but then, only the upper bound is explicit; is this a 0-based or a 1-based array? If the module says Option Base 1, then it's 1-based. Otherwise, it's 0-based - implicit array lower bounds are an easy source of "off-by-one" bugs (like how you're populating the arrays starting at index 1, leaving index 0 empty). Always make array bounds explicit:
ReDim IT(1 To BT) As String
Unclear why you need 3 arrays at all, and why you're only populating (i,i) in the 3rd one - you cannot populate a 2D array with a Do...Loop structure; you need every value of y for each value of x, and unless you hard-code the width of the array, that's a nested loop.
Moreover, looping on the ActiveCell and Selecting an Offset is making the code 1) very hard to follow, and 2) incredibly inefficient.
Consider:
Dim lastRow As Long
lastRow = Sheet1.Range("B" & Sheet1.Rows).End(xlUp).Row
ReDim values(1 To lastRow, 1 To 2) As Variant
Dim currentRow As Long
For currentRow = 2 To lastRow
Dim currentColumn As Long
For currentColumn = 1 To 2
values(currentRow, currentColumn) = Sheet1.Cells(currentRow, currentColumn).Value
Next
Next
Now, if we don't need any kind of logic in that loop and all we want is to grab a 2D variant array that contains every cell in B2:B???, then we don't need any loops:
Dim values As Variant
values = Sheet1.Range("A2:B" & lastRow).Value
And done: values is a 1-based (because it came from a Range), 2D variant array that contains the values of every cell in A2:B{lastRow}.
Note, code that consumes this array will need to avoid assumptions about the data types in it.
As #SJR has said, variant will allow for this. The below example is a easy example how to add different types to an array. Instead of x or y you can have a cell on a worksheet.
Dim array1() As Variant, i As Long
Dim x As String, y As Long
x = "5"
y = 1
For i = 1 To 10
ReDim Preserve array1(1 To 2, 1 To i)
array1(1, i) = x
array1(2, i) = y
y = y + 1
Debug.Print array1(1, i) & "," & array1(2, i) ' This is where you insert output
Next
You can do this:
BT = Range("A12")
ReDim IT(BT) As String
ReDim RBT(BT) As Integer
Dim IT_RBT(1 to 2) 'variant
IT_RBT(1) = IT 'add String array
IT_RBT(2) = RBT 'add Integer array
... this will keep your typed arrays functional but it's not a 2D array and you'd need to use notation like
IT_RBT(1)(1) 'String type
IT_RBT(2)(1) 'Integer type
I'm trying to write a simple push function that can add an element to my VBA arrays.
I can't figure out how to allow it to accept typed arrays. So far, I can get the function to accept arrays explicitly typed as "Variant". See below
Function push(arr() As Variant, el As Variant) As Variant()
' function to redim an array and increment it by one. If array is length 1 and element is empty, it will place the "el" param in the first index
If IsEmpty(arr(UBound(arr))) And UBound(arr) = LBound(arr) Then
arr(UBound(arr)) = el
Else
ReDim Preserve arr(LBound(arr) To UBound(arr) + 1)
arr(UBound(arr)) = el
End If
push = arr
End Function
Sub testPush()
Dim myArr() As Variant
Dim newArr() As Variant
myArr = Array("apple", "banana", "4")
myArr = push(myArr, "coconut")
Debug.Print Join(myArr, ", ")
newArr = Array(1, 2, 3, 4)
newArr = push(newArr, 7)
Debug.Print Join(newArr, ", ")
End Sub
When I dimension myArr as string, e.g., Dim myArr() as String I see a compile error in my push function: Type mismatch: array or user defined type expected. Is there any way I can use a single function that attempts to increment an element to the end of an array, regardless of the array type?
Contrary to what I write below, maybe someone who knows more than me will tell you this is possible and exactly how you can do this.
I see that your function returns an array of variants:
Function push(arr() As Variant, el As Variant) As Variant()
which, if I understand correctly, can only be assigned to a variable of type Variant or Variant().
If I change the function to:
Function push(arr As Variant, el As Variant) As Variant
I think the function can now accept an array of any type1 but it still returns a Variant -- which I believe can't be assigned to every typed array (meaning you'll still get a compiler error in some cases).
What might be an easier approach is to change the Function to a Sub and have the subroutine modify the array in-place. That way there is no assignment at the call site (since subroutines do not return values) and the code should compile. This also means any type error2 will now occur at run-time.
Also, it's not clear to me what the point of the below is:
If IsEmpty(arr(UBound(arr)))
It seems like you're checking if the array's first element has been assigned something other than its default initialised value. But that check seems like it will only work for Variant types (which are initialised as empty). I think strings are initialised as "", numbers are initialised as 0, objects are initialised as Nothing, and so on. In short, I think the IsEmpty check may return false negative for types other than Variant. The aforementioned behaviour might be what you want, or it might not be (given that you say you want this Push code to work with arrays of any type).
Overall, one approach could be something like:
Option Explicit
Sub Push(ByRef someArray As Variant, ByVal someElement As Variant)
' This routine expects array to be 1-dimensional.
' and will modify the array in-place.
' The array must by dynamic (cannot Redim an array that
' was statically declared).
Dim lastIndex As Long
lastIndex = UBound(someArray)
Dim arrayNeedsExtending As Boolean
' If you are using "IsEmpty" to work out if the first element
' has been assigned a value other than its default value at initialisation
' then you may need to see: https://stackoverflow.com/a/3331239/8811778
' as "IsEmpty" might only work for Variants and may return false
' negatives otherwise.
arrayNeedsExtending = (lastIndex <> LBound(someArray)) Or Not IsEmpty(someArray(lastIndex))
If arrayNeedsExtending Then
ReDim Preserve someArray(LBound(someArray) To (lastIndex + 1))
End If
' If you have an array of objects (hypothetically, instead of a collection), the line below
' will raise a syntax error since Set keyword is required for objects.
someArray(UBound(someArray)) = someElement
End Sub
Private Sub TestPush()
Dim someStrings() As String
someStrings = Split("a,a", ",", -1, vbBinaryCompare)
Push someStrings, "b"
Debug.Assert (JoinArray(someStrings) = "a,a,b")
Dim someBooleans() As Boolean ' Can only Push dynamic arrays
ReDim someBooleans(0 To 1)
Push someBooleans, True
Debug.Assert (JoinArray(someBooleans) = "False,False,True")
Dim someZeros() As Long
ReDim someZeros(0 To 1)
Push someZeros, 0
Debug.Assert (JoinArray(someZeros) = "0,0,0")
End Sub
Private Function JoinArray(ByRef someArray As Variant, Optional delimiter As String = ",") As String
' Expects array to be 1-dimensional.
' Attempts to handle non-string types albeit without error handling.
Dim toJoin() As String
ReDim toJoin(LBound(someArray) To UBound(someArray))
Dim arrayIndex As Long
For arrayIndex = LBound(someArray) To UBound(someArray)
toJoin(arrayIndex) = CStr(someArray(arrayIndex)) ' Will throw if type cannot be coerced into string.
Next arrayIndex
JoinArray = Join(toJoin, delimiter)
End Function
1 Maybe not user-defined types though.
2 Say you pass the string"a" to be pushed into a Long-typed array -- or any value that cannot be type-coerced into the array's type.
I am having some trouble getting a function I have been writing to work properly. I need it to take an array that is sized 1 to x, and transfer it to a new array which is sized 0 to x-1. I would think it would work just like this:
Private Function ShiftDownArray(theArray() As String) As String()
Dim a As Integer
ReDim ShiftDownArray(LBound(theArray) - 1 To UBound(theArray) - 1)
For a = LBound(theArray) To UBound(theArray)
ShiftDownArray(a - 1) = theArray(a)
Next a
End Function
But I get a compile error: Function call on left-hand side of assignment must return Variant or Object. The documentation on this error essentially says delete that line to make it work, which doesn't point me in the right direction. I have tried changing the type to variant, but it starts a chain reaction of needing to change array type from string to variant, and it leads to issues in other parts of my program.
Is there any way to approach this that will allow me to retain the string array type? Thanks
You can pass arrays of most types as a Variant between methods and procedures in VBA
Private Function ShiftDownArray(ByRef theArray As Variant) As Variant
Dim i As Integer
ReDim x(0 To UBound(theArray) - 1) As Variant
For i = 0 To UBound(x)
x(i) = theArray(i + 1)
Next i
ShiftDownArray = x
End Function
But more importantly - why would you want to do this anyway? You can just -/+ 1 to the index in the original array?
Is this what you are looking for:
Public Sub ShiftArrayTest()
'Make an 1-bound array
Dim arr1() As String, N As Long, i As Long
N = 10
ReDim arr1(1 To N)
For i = 1 To N
arr1(i) = CStr(i)
Next i
'Now for the shift
Dim arr2() As String
arr2 = ShiftArray(arr1)
End Sub
Public Function ShiftArray(ByRef theArray() As String) As String()
'Now for the shift
Dim i1 As Long, N As Long, i As Long, res() As String
i1 = LBound(theArray): N = UBound(theArray) - i1 + 1
ReDim res(0 To N - 1)
For i = 0 To N - 1
res(i) = theArray(i1 + i)
Next i
ShiftArray = res
End Function
What I am doing here is taking any array and converting it into a 0-bound array.
The Error is most probably in this line: 'ReDim ShiftDownArray(LBound(theArray) - 1 To UBound(theArray) - 1)'
It looks like you're recursively calling itself, which seems odd given there isn't a base case.
See the following example as provided by this website. The gist of it is that it will skip the first element and copy everything over 'to the left'.
Function Array_Shift(arr As Variant) As Variant
' http://www.php.net/manual/en/function.array-shift.php
Dim tempArray As Variant
Dim i As Long
tempArray = arr
' shift elements one position up
' by skipping the first element of the source array
For i = LBound(tempArray) To UBound(tempArray) - 1
tempArray(i) = tempArray(i + 1)
Next i
' remove last element
' which is now empty
ReDim Preserve tempArray(LBound(tempArray) To UBound(tempArray) - 1)
Array_Shift = tempArray
End Function
The trouble is not with passing an array of strings as a parameter - you can pass arrays of any type, as far as I'm aware. However, assigning a value to ShiftDownArray(a - 1) is ambiguous as you could be accessing the a-1th element of the array or passing a - 1 as a parameter to the ShiftDownArray() function.
The Function call on left-hand side of assignment must return Variant or Object. error message hints to this. You are calling the ShiftDownArray() function rather than accessing the array. The compiler knows you are going to assign something to the value returned by the function (because it is followed by an =) but doesn't know the type as it hasn't yet evaluated theArray(a). To ensure that the assignment can complete regardless of the type of theArray(a), the compiler tries to make sure that ShiftDownArray() returns a Variant or Object to which anything can be assigned.
To avoid this error you can create a temporary array which can be accessed in the conventional manner and assign that array to ShiftDownArray to return from the function.
The following code shows this:
Private Function ShiftDownArray(theArray() As String) As String()
ReDim tempArray(LBound(theArray) - 1 To UBound(theArray) - 1) As String
Dim i As Integer
For i = LBound(tempArray) To UBound(tempArray)
tempArray(i) = theArray(i + 1)
Next i
ShiftDownArray = tempArray
End Function
I'm working on an Excel VBA addin that exchanges objects with a COM server, something like this:
'get an array of objects
Dim Ents() As ISomething
ComObject.GetEntities Ents
'send an array with 10 objects
ReDim Ents(9)
Set Ents(0) = ...
...
ComObject.SetEntities Ents
Getting the arrays works well: if the array contains objects it works as expected, if the array is empty then UBound(Ents) = -1 and everything works as expected.
Sending the arrays works only with not empty arrays, because I can't Redim Ents(-1), and Eraseing the array both VBA and the COM server crash: Debug.Print UBound(Ents) crashes in VBA and who knows what crashes the server.
It looks like the Erase statement leaves the array undefined/corrupted rather than empty.
EDIT (clarification to a comment below):
Executing this code it crashes because it can't calculate the UBound:
Sub Test()
Dim Ents() As ISmartId
Debug.Print UBound(Ents)
End Sub
But if you add Ents to the watch window, then set a break point to the Debug.Print line and execute, the debugger shows the ISmartId(0 to -1) in the Type column. After this the execution continues without crash, and the Debug window shows the expected -1.
It looks like the debugger was able to correctly initialize the empty array the way I need it just to show its value.
For objects, you can do this just by copying an undefined array into a variant and back:
Dim o() As Worksheet
Dim v As Variant
v = o
o = v
For non-objects, make an empty array in a variant and then change its type code:
Private Declare Sub GetMem2 Lib "msvbvm60" (src As Any, dest As Any)
Dim i() as Long
Dim v as Variant
v = Array()
Dim NewTypeCode As Integer
NewTypeCode = vbArray Or vbLong
GetMem2 NewTypeCode, v
i = v
If you need a fresh array you could create a "factory" function to return one
Function FreshArray() As ISomething()
Dim rv() As ISomething
FreshArray = rv
End Function
Ents = FreshArray()
ComObject.GetEntities Ents