How to create multi-dimensional array of unknown size? - arrays

I've got a simple problem:
I've got a set of data, which I'm sifting through and adding into an array upon criteria match
Issue is, I don't know how many matches there might be, so I need the array to be of unspecified size.
The second index of the array is static.
In an (pseudo-language) example:
if <matched criteria> = True {
i = i + 1
array( i, 1 ) => "John Doe" ' name
array( i, 2 ) => "New York" ' location
array( i, 3 ) => "02. 08. 1992" ' birthdate
}
Issue is, in vba you have to kind of pre-declare the arrays (especially with Option Explicit enabled). My thought process was to declare an array, that would start with first index at 0 and I would gradually ReDim it upon need.
Here is an simplified example of my code:
Dim cell as Range
Dim arr(0, 1 to 3) as String
Dim i As Integer: i = 0
For each cell in Range("A1:A100")
If criteria_match(cell) = True Then
arr(i, 1) = Cells(cell.row, 4)
arr(i, 2) = Cells(cell.row, 5)
arr(i, 3) = Year(Cells(cell.row, 6))
i = i + 1
ReDim Preserve arr(i, 1 to 3)
End If
Next cell
Issue is, this throws an exception:
Is there perhaps any way, I could steadily increase the size of the first array index depending on the need?

Don't size the array in the variable declaration statement.
Change:
Dim arr(0, 1 to 3) as String
to:
Dim arr() as String
ReDim arr(1 to 3, i)
Redimension it as necessary.
Edit:
For more information, see this link: https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/array-already-dimensioned
To briefly summarize, when you size the array in the declaration statement, it creates a static array (which can't be resized). When you don't declare a size, then it becomes a dynamic array, which can be resized.
An important note to make: ReDim Preserve can only be applied on the last dimension of the array
eg. ReDim Preserve arr(1 to 3, i) will work.
Meanwhile, ReDim Preserve arr (i, 1 to 3) will not.

Use a Type for the data and a collection for the variables based on the type.
Class Person
Public Name As String
Public Location As String
Public DoB As Date
In a Module
Sub Test()
Dim this_person As Person
Dim Persons As Collection
Dim my_cell As Excel.Range
Set Persons = New Collection
For Each my_cell In Range("A1:A100")
If Criteria_Match(my_cell) Then
Set this_person = New Person
With this_person
.Name = ActiveWorkbook.ActiveWorksheet.Cells(my_cell.Row, 4).Value2
.Location = ActiveWorkbook.ActiveWorksheet.Cells(my_cell.Row, 5).Value2
.DoB = Year(ActiveWorkbook.ActiveWorksheet.Cells(my_cell.Row, 6).Value2)
End With
Persons.Add this_person
End If
Next
End Sub

Related

Write VBA that sets each element in 2nd column of an array, to a new nested array of size n, where n is an integer from the 1st column

For any 2-column array A, set every 2nd-column element to be a 1-dimensional array with a size indicated by the first column.
For example: the row [3,""] would become [3,("","","")]
Something like this?
Option Base 1
Sub NestArrays()
Dim OuterArray(3,2) as variant 'create OuterArray with hypothetical numbers 3,5,7
OuterArray(1,1) = 3
OuterArray(2,1) = 5
OuterArray(3,1) = 7
for i = 1 to Ubound(OuterArray) ' for every row in OuterArray
size = OuterArray(i,1) ' set variable "size" equal to the element in 1st column
OuterArray(i,2)=Array(1 to size) ' <--- this doesn't work, set 2nd-col element = array(size)
next i
''''test
OuterArray(1,2)(1) = "aardvark"
debug.print OuterArray(1,2)(1)
End Sub
Use an intermediate array and ReDim as size is dynamic. You possibly want a safeguard on size as well as it needs to be >0 (just a thought)
Option Explicit
Option Base 1
Public Sub NestArrays()
Dim outerArray(3, 2) As Variant, i As Long, size As Long 'create OuterArray with hypothetical numbers 3,5,7
outerArray(1, 1) = 3
outerArray(2, 1) = 5
outerArray(3, 1) = 7
Dim arr()
For i = 1 To UBound(outerArray) ' for every row in OuterArray
size = outerArray(i, 1) ' set variable "size" equal to the element in 1st column
ReDim arr(1 To size)
outerArray(i, 2) = arr
Next i
''''test
outerArray(1, 2)(1) = "aardvark"
Debug.Print outerArray(1, 2)(1)
End Sub
The host array is declared to hold items of type variant. As you can assign an array to a Variant you can proceed as follows
Dim myIndex as long
For Lbound(OuterArray,1) to Ubound(OuterArray,1)
OuterArray(myIndex,2) = Split(String$(OuterArray(myIndex,1),","),",")
Next
If you need the new array to be also an array of variants you will need to create a function that generates the appropriate size of array.
Public Function VarArray(ByVal ipCount As Long) As Variant
Dim mySd As Object
Set mySd = CreateObject("Scripting.DIctionary")
Dim myIndex As Long
For myIndex = 1 To ipCount
mySd.Add myIndex, 0
Next
VarArray = mySd.Items
End Function
So that
OuterArray(myIndex,2) = Split(String$(OuterArray(myIndex,1),","),",")
becomes
OuterArray(myIndex,2) =VarArray(OuterArray(myIndex,1))

VLOOKUP() Alternative using Arrays

I’ve been experimenting with arrays to find a faster alternative to VLOOKUP(), which can take a long time to execute with very large data sets.
I searched SO and many other sites, grabbing snippets of code.
The data:
A1:A5 the list of values to lookup (1,2,3,4,5)
C1:C5 the range to ‘find’ the values (2,4,6,8,10)
D1:D5 the range of values to ‘return’ (a,b,c,d,e)
B1:B5 is where I’d like to paste the ‘looked-up’ values.
The code works up to a point, in that it does return correct values for the ‘looked-up’ value’s position in C1:C5 – and the correct values in the adjacent cells in D1:D5.
When I try to load the returned values to Arr4 (the array to be pasted back to the sheet) which is saying <Type mismatch> when I hover the mouse over it. It doesn’t stop the code from executing, but it doesn’t paste anything.
My questions are:
How do I populate the array Arr4 with the myVal2 values, and
How do I paste it back to the sheet?
Option Explicit
Sub testArray()
Dim ArrLookupValues As Variant
ArrLookupValues = Sheet1.Range("A1:A5") 'The Lookup Values
Dim ArrLookupRange As Variant
ArrLookupRange = Sheet1.Range("C1:C5") 'The Range to find the Value
Dim ArrReturnValues As Variant
ArrReturnValues = Sheet1.Range("D1:D5") 'The adjacent Range to return the Lookup Value
Dim ArrOutput As Variant 'output array
Dim UpperElement As Long
UpperElement = UBound(ArrLookupValues) 'Used purely for the ReDim statement
Dim i As Long
For i = LBound(ArrLookupValues) To UBound(ArrLookupValues)
Dim myVal As Variant
myVal = ArrLookupValues(i, 1)
Dim pos As Variant 'variant becaus it can return an error
pos = Application.Match(myVal, ArrLookupRange, 0) 'pos always returns the correct position
Dim myVal2 As Variant
If Not IsError(pos) Then
myVal2 = ArrReturnValues(pos, 1) 'myVal2 always returns the correct value
ReDim Preserve ArrOutput(1 To UpperElement, 1 To 1)
ArrOutput(i, 1) = myVal2
Else
ReDim Preserve ArrOutput(1 To UpperElement, 1 To 1)
myVal2 = "Not Found"
ArrOutput(i, 1) = myVal2
End If
Next i
Dim Destination As Range
Set Destination = Range("B1")
Destination.Resize(UBound(ArrOutput, 1), UBound(ArrOutput, 2)).Value =
ArrOutput
End Sub
According to #T.M 's answer, you can even do that without looping just by using VLookup instead of Match:
Public Sub testArraya()
With Sheet1
Dim ArrLookupValues() As Variant
ArrLookupValues = .Range("A1:A5").Value ' lookup values 1,2,3,4,5,6
Dim ArrLookupReturnRange() As Variant ' lookup range items 2,4,6,8,10
ArrLookupReturnRange = .Range("C1:D5").Value ' And return column D a,b,c,d,e
End With
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'[1] Match all values at once and return other values of column D
' (found position indices or Error 2042 if not found)
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Dim ArrOutput() As Variant
ArrOutput = Application.VLookup(ArrLookupValues, ArrLookupReturnRange, 2, 0)
'[3] write results to any wanted target
Dim Destination As Range
Set Destination = Sheet1.Range("B1") ' ‹‹ change to your needs
Destination.Resize(UBound(ArrOutput, 1), UBound(ArrOutput, 2)).Value = ArrOutput
End Sub
Or even extremly shortened and almost a one liner:
Public Sub testArrayShort()
Const nRows As Long = 5 'amount of rows
With Sheet1
.Range("B1").Resize(nRows).Value = Application.VLookup(.Range("A1").Resize(nRows).Value, .Range("C1:D1").Resize(nRows).Value, 2, 0)
End With
End Sub
Use proper error handling and an If statement instead of On Error Resume Next.
Also your Arr4 needs to be 2 dimensional like your other arrays. Even if it is only one column it needs no be Arr4(1 To UpperElement, 1 To 1) and Arr4(i, 1) = myVal2. Ranges are always 2 dimensional (row, column) even if there is only one column.
And I highly recommend to rename your array variables. When ever you feel like you have to give your variable numbers, you can be sure you are doing it wrong.
Rename them like following for example:
Arr1 --› ArrLookupValues
Arr2 --› ArrLookupRange
Arr3 --› ArrReturnValues
Arr4 --› ArrOutput
This is only a simple modification but your code will extremely gain in human readability and maintainability. You even don't need comments to describe the arrays because their names are self descriptive now.
Finally your output array can be declared the same size as the input arrays. Using ReDim Preserve makes your code slow, so avoid using it.
So something like this should work:
Option Explicit
Public Sub testArray()
Dim ArrLookupValues() As Variant
ArrLookupValues = Sheet1.Range("A1:A5").Value
Dim ArrLookupRange() As Variant
ArrLookupRange = Sheet1.Range("C1:C5").Value
Dim ArrReturnValues() As Variant
ArrReturnValues = Sheet1.Range("D1:D5").Value
Dim UpperElement As Long
UpperElement = UBound(ArrLookupValues, 1)
'create an empty array (same row count as ArrLookupValues)
ReDim ArrOutput(1 To UpperElement, 1 To 1)
Dim i As Long
For i = LBound(ArrLookupValues, 1) To UBound(ArrLookupValues, 1)
Dim FoundAt As Variant 'variant because it can return an error
FoundAt = Application.Match(ArrLookupValues(i, 1), ArrLookupRange, 0) 'pos always returns the correct position
If Not IsError(FoundAt) Then
ArrOutput(i, 1) = ArrReturnValues(FoundAt, 1)
Else
ArrOutput(i, 1) = "Not Found"
End If
Next i
Dim Destination As Range
Set Destination = Range("B1") 'make sure to specify a sheet for that range!
Destination.Resize(UBound(ArrOutput, 1), UBound(ArrOutput, 2)).Value = ArrOutput
End Sub
Just for fun a slight modification of #PEH 's valid approach demonstrating a rather unknown way to excecute a single Match checking both arrays instead of repeated matches:
Public Sub testArray()
With Sheet1
Dim ArrLookupValues As Variant
ArrLookupValues = .Range("A1:A5").Value ' lookup values 1,2,3,4,5,6
Dim ArrLookupRange As Variant ' lookup range items 2,4,6,8,10
ArrLookupRange = .Range("C1:C5").Value
Dim ArrReturnValues As Variant ' return column D a,b,c,d,e
ArrReturnValues = .Range("D1:D5").Value
End With
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'[1] Match all item indices within ArrLookupRange at once
' (found position indices or Error 2042 if not found)
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Dim ArrOutput
ArrOutput = Application.Match(ArrLookupValues, ArrLookupRange, 0)
'[2] change indices by return values
Dim i As Long
For i = 1 To UBound(ArrOutput)
If Not IsError(ArrOutput(i, 1)) Then
ArrOutput(i, 1) = ArrReturnValues(ArrOutput(i, 1), 1)
' Else
' ArrOutput(i, 1) = "Not Found" ' optional Not Found statement instead of #NV
End If
Next i
'[3] write results to any wanted target
Dim Destination As Range
Set Destination = Sheet1.Range("B1") '<< change to your needs
Destination.Resize(UBound(ArrOutput, 1), UBound(ArrOutput, 2)).Value = ArrOutput
End Sub

Multidimensional array or collection

I have code:
Dim products As Variant
LastRow = ActiveSheet.Cells(Rows.Count, 1).End(xlUp).Row
products = Array("MS-CHOPMAT-6", "MS-BOARDS-3", "MS-CHOP-LR")
For x = LastRow To 1 Step -1
order_quantity = Range("$E$" & x).Value
item_price = Range("$F$" & x).Value
' if value not found inside the array using the "MATCH" function
If IsError(Application.Match(Range("$D$" & x).Value, products, 0)) Then
Range("$H$" & x).Value = "ERROR - " & order_quantity
Else ' successful "MATCH" inside the array
Range("$H$" & x).Value = order_quantity * 3
End If
Next
but instead of having simple array I need array multidimensional or "collections". How to change this code to work with collections or multidimensional array
like:
products = Array(Array("MS-CHOPMAT-6", 11,"w"), Array("MS-BOARDS-3", 12, 4), Array("MS-CHOP-LR", 13, 5))
The two dimensional answer to this would be as below. There are many way to do this, this is simply an example. Two dimensional arrays are great but need some thought around implementation, ideally you would want to use some form of recursion to populate it, the example below simple sets them in a static manner.
Public Sub Sample()
Dim AryTable() As String
Dim LngRow As Long
Dim LngCol As Long
'Below is a two dimensional array, think of it as a
'table with 3 rows and 5 columns (the base is zero
'so it is not 2 rows and 4 columns as it may look)
ReDim AryTable(2, 4)
'We can then populate (or not) each 'cell' of the array
'Row 1
AryTable(0, 0) = "1"
AryTable(0, 1) = "Field1"
AryTable(0, 2) = "Field2"
AryTable(0, 3) = "Field3"
'Row 2
AryTable(1, 0) = "2"
AryTable(1, 1) = "Field1"
AryTable(1, 2) = "Field2"
AryTable(1, 3) = "Field3"
AryTable(1, 4) = "Field4"
'Row 3
AryTable(2, 0) = "3"
AryTable(2, 1) = "Field1"
AryTable(2, 2) = "Field2"
AryTable(2, 4) = "Field4"
'Ubound by the first dimension to go through the rows
For LngRow = 0 To UBound(AryTable, 1)
'Ubound by the second dimension to go through the columns
For LngCol = 0 To UBound(AryTable, 2)
Debug.Print AryTable(LngRow, 0) & ": " & AryTable(LngRow, LngCol)
Next
Next
End Sub
Point to note, if you don't declare the size of the array at the start you can change it later.
This is declared (and can not be changed later): -
Dim AryTable(1,2) as string
This is not declared (and can be changed later): -
Dim AryTable() as string
When yo have not declared its size (so can change it) you must size it before use. There are two ways to do it, reset or preserve.
This will clear the array and set it to the new size, I.e. If the array was previously 100 in size and had data it in, the below would remove all the data but make it larger.
Redim AryTable(200)
If the array was previously 100 in size and had data it in, the below would retain all the data and make it larger
Redim Preserve AryTable(200)
On a two dimensional array you can only adjust the second dimension. The below is ok: -
Redim AryTable(2,4)
Redim Preserve AryTable(2,8)
The below will fail: -
Redim AryTable(2,4)
Redim Preserve AryTable(4,8)
With this in mind if you want to use a two dimensional array to store data like a table, use the first dimension to be columns and the second to be the rows, columns counts rarely change but row may be added.
This may help, an array in an array is not strictly possible, what you can do what I have done in the past is mimic an array in an array action by creating your own delimiter as shown below.
Public Sub Sample()
Dim AryTable() As String
Dim AryRow() As String
Dim VntCell As Variant
Dim LngID As Long
'AryTable is root array
ReDim AryTable(2)
'Below is the population of the array, using #~# as a delimiter, whatever
'you feel will not come up will be best
'In the past to be safe I used #UnliklyDivider# as my delimiter, to make
'sure it was never confused or come up in
'real data
AryTable(0) = "1#~#Field1#~#Field2#~#Field3"
AryTable(1) = "1#~#Field1#~#Field2#~#Field3#~#Field4#~#Field5"
AryTable(2) = "1#~#Field1#~#Field2#~#Field3#~##~#Field5"
'This goes through each row in the array, using each one as an array in its
'own right
For LngID = 0 To UBound(AryTable, 1)
AryRow = Split(AryTable(LngID), "#~#")
For Each VntCell In AryRow
Debug.Print LngID & ": " & VntCell
Next
Next
End Sub

Print Dynamic Error Array to Sheet

I'm having troubles getting my Error array to print to a range. I'm fairly sure I'm resizing it incorrectly, but I'm not sure how to fix it. I created a test add which just added garbage data from columns A and B, but normally AddPartError would be call from within various Subs/Functions, and then at the end of the main script process the array should be dumped onto a sheet. Here are the relevant functions:
Sub testadd()
For Each i In ActiveSheet.Range("A1:A10")
Call AddPartError(i.value, i.Offset(0, 1))
Next i
tmp = PartErrors
PrintArray PartErrors, ActiveWorkbook.Worksheets("Sheet1").[D1]
Erase PartErrors
tmp1 = PartErrors
PartErrorsDefined = 0
End Sub
Sub PrintArray(Data As Variant, Cl As Range)
Cl.Resize(UBound(Data, 1), 2) = Data
End Sub
Private Sub AddPartError(part As String, errType As String)
If Not PartErrorsDefined = 1 Then
ReDim PartErrors(1 To 1) As Variant
PartErrorsDefined = 1
End If
PartErrors(UBound(PartErrors)) = Array(part, errType)
ReDim Preserve PartErrors(1 To UBound(PartErrors) + 1) As Variant
End Sub
Ok. I did a bit of checking and the reason this doesn't work is because of your array structure of PartErrors
PartErrors is a 1 dimensional array and you are adding arrays to it, so instead of multi dimentional array you end up with a jagged array, (or array of arrays) when you actually want a 2d array
So to fix this, I think you need to look at changing your array to 2d. Something like the below
Private Sub AddPartError(part As String, errType As String)
If Not PartErrorsDefined = 1 Then
ReDim PartErrors(1 To 2, 1 To 1) As Variant
PartErrorsDefined = 1
End If
PartErrors(1, UBound(PartErrors, 2)) = part 'Array(part, errType)
PartErrors(2, UBound(PartErrors, 2)) = errType
ReDim Preserve PartErrors(1 To 2, 1 To UBound(PartErrors, 2) + 1) As Variant
End Sub
and
Sub PrintArray(Data As Variant, Cl As Range)
Cl.Resize(UBound(Data, 2), 2) = Application.Transpose(Data)
End Sub
NB. You also need to Transpose your array to fit in the range you specified.
You code is a little hard to follow, but redim clears the data that is in the array, so I think you need to use the "Preserve" keyword.
Below is some example code you can work through to give you the idea of how it works, but you will need to spend some time working out how to fit this into your code.
Good luck!
Sub asda()
'declare an array
Dim MyArray() As String
'First time we size the array I do not need the "Preserve keyword
'there is not data in the array to start with!!!
'Here we size it too 2 by 5
ReDim MyArray(1, 4)
'Fill Array with Stuff
For i = 0 To 4
MyArray(0, i) = "Item at 0," & i
MyArray(1, i) = "Item at 1," & i
Next
' "Print" data to worksheet
Dim Destination1 As Range
Set Destination1 = Range("a1")
Destination1.Resize(UBound(MyArray, 1) + 1, UBound(MyArray, 2) + 1).Value = MyArray
'Now lets resize that arrray
'YOU CAN ONLY RESIZE THE LAST SIZE OF THE ARRAY - in this case 4 to 6...
ReDim Preserve MyArray(1, 6)
For i = 5 To 6
MyArray(0, i) = "New Item at 0," & i
MyArray(1, i) = "New Item at 1," & i
Next
'and let put that next to our first list
' "Print" data to worksheet
Dim Destination2 As Range
Set Destination2 = Range("A4")
Destination2.Resize(UBound(MyArray, 1) + 1, UBound(MyArray, 2) + 1).Value = MyArray
End Sub

ReDim Preserve to a multi-dimensional array in VB6

I'm using VB6 and I need to do a ReDim Preserve to a Multi-Dimensional Array:
Dim n, m As Integer
n = 1
m = 0
Dim arrCity() As String
ReDim arrCity(n, m)
n = n + 1
m = m + 1
ReDim Preserve arrCity(n, m)
Whenever I do it as I have written it, I get the following error:
runtime error 9: subscript out of range
Because I can only change the last array dimension, well in my task I have to change the whole array (2 dimensions in my example) !
Is there any workaround or another solution for this?
As you correctly point out, one can ReDim Preserve only the last dimension of an array (ReDim Statement on MSDN):
If you use the Preserve keyword, you can resize only the last array
dimension and you can't change the number of dimensions at all. For
example, if your array has only one dimension, you can resize that
dimension because it is the last and only dimension. However, if your
array has two or more dimensions, you can change the size of only the
last dimension and still preserve the contents of the array
Hence, the first issue to decide is whether 2-dimensional array is the best data structure for the job. Maybe, 1-dimensional array is a better fit as you need to do ReDim Preserve?
Another way is to use jagged array as per Pieter Geerkens's suggestion. There is no direct support for jagged arrays in VB6. One way to code "array of arrays" in VB6 is to declare an array of Variant and make each element an array of desired type (String in your case). Demo code is below.
Yet another option is to implement Preserve part on your own. For that you'll need to create a copy of data to be preserved and then fill redimensioned array with it.
Option Explicit
Public Sub TestMatrixResize()
Const MAX_D1 As Long = 2
Const MAX_D2 As Long = 3
Dim arr() As Variant
InitMatrix arr, MAX_D1, MAX_D2
PrintMatrix "Original array:", arr
ResizeMatrix arr, MAX_D1 + 1, MAX_D2 + 1
PrintMatrix "Resized array:", arr
End Sub
Private Sub InitMatrix(a() As Variant, n As Long, m As Long)
Dim i As Long, j As Long
Dim StringArray() As String
ReDim a(n)
For i = 0 To n
ReDim StringArray(m)
For j = 0 To m
StringArray(j) = i * (m + 1) + j
Next j
a(i) = StringArray
Next i
End Sub
Private Sub PrintMatrix(heading As String, a() As Variant)
Dim i As Long, j As Long
Dim s As String
Debug.Print heading
For i = 0 To UBound(a)
s = ""
For j = 0 To UBound(a(i))
s = s & a(i)(j) & "; "
Next j
Debug.Print s
Next i
End Sub
Private Sub ResizeMatrix(a() As Variant, n As Long, m As Long)
Dim i As Long
Dim StringArray() As String
ReDim Preserve a(n)
For i = 0 To n - 1
StringArray = a(i)
ReDim Preserve StringArray(m)
a(i) = StringArray
Next i
ReDim StringArray(m)
a(n) = StringArray
End Sub
Since VB6 is very similar to VBA, I think I might have a solution which does not require this much code to ReDim a 2-dimensional array - using Transpose, if you are working in Excel.
The solution (Excel VBA):
Dim n, m As Integer
n = 2
m = 1
Dim arrCity() As Variant
ReDim arrCity(1 To n, 1 To m)
m = m + 1
ReDim Preserve arrCity(1 To n, 1 To m)
arrCity = Application.Transpose(arrCity)
n = n + 1
ReDim Preserve arrCity(1 To m, 1 To n)
arrCity = Application.Transpose(arrCity)
What is different from OP's question: the lower bound of arrCity array is not 0, but 1. This is in order to let Application.Transpose do it's job.
Note that Transpose is a method of the Excel Application object (which in actuality is a shortcut to Application.WorksheetFunction.Transpose). And in VBA, one must take care when using Transpose as it has two significant limitations: If the array has more than 65536 elements, it will fail. If ANY element's length exceed 256 characters, it will fail. If neither of these is an issue, then Transpose will nicely convert the rank of an array form 1D to 2D or vice-versa.
Unfortunately there is nothing like 'Transpose' build into VB6.
In regards to this:
"in my task I have to change the whole array (2 dimensions"
Just use a "jagged" array (ie an array of arrays of values). Then you can change the dimensions as you wish. You can have a 1-D array of variants, and the variants can contain arrays.
A bit more work perhaps, but a solution.
I haven't tested every single one of these answers but you don't need to use complicated functions to accomplish this. It's so much easier than that! My code below will work in any office VBA application (Word, Access, Excel, Outlook, etc.) and is very simple. Hope this helps:
''Dimension 2 Arrays
Dim InnerArray(1 To 3) As Variant ''The inner is for storing each column value of the current row
Dim OuterArray() As Variant ''The outer is for storing each row in
Dim i As Byte
i = 1
Do While i <= 5
''Enlarging our outer array to store a/another row
ReDim Preserve OuterArray(1 To i)
''Loading the current row column data in
InnerArray(1) = "My First Column in Row " & i
InnerArray(2) = "My Second Column in Row " & i
InnerArray(3) = "My Third Column in Row " & i
''Loading the entire row into our array
OuterArray(i) = InnerArray
i = i + 1
Loop
''Example print out of the array to the Intermediate Window
Debug.Print OuterArray(1)(1)
Debug.Print OuterArray(1)(2)
Debug.Print OuterArray(2)(1)
Debug.Print OuterArray(2)(2)
I know this is a bit old but I think there might be a much simpler solution that requires no additional coding:
Instead of transposing, redimming and transposing again, and if we talk about a two dimensional array, why not just store the values transposed to begin with. In that case redim preserve actually increases the right (second) dimension from the start. Or in other words, to visualise it, why not store in two rows instead of two columns if only the nr of columns can be increased with redim preserve.
the indexes would than be 00-01, 01-11, 02-12, 03-13, 04-14, 05-15 ... 0 25-1 25 etcetera instead of 00-01, 10-11, 20-21, 30-31, 40-41 etcetera.
As long as there is only one dimension that needs to be redimmed-preserved the approach would still work: just put that dimension last.
As only the second (or last) dimension can be preserved while redimming, one could maybe argue that this is how arrays are supposed to be used to begin with.
I have not seen this solution anywhere so maybe I'm overlooking something?
(Posted earlier on similar question regarding two dimensions, extended answer here for more dimensions)
You can use a user defined type containing an array of strings which will be the inner array. Then you can use an array of this user defined type as your outer array.
Have a look at the following test project:
'1 form with:
' command button: name=Command1
' command button: name=Command2
Option Explicit
Private Type MyArray
strInner() As String
End Type
Private mudtOuter() As MyArray
Private Sub Command1_Click()
'change the dimensens of the outer array, and fill the extra elements with "1"
Dim intOuter As Integer
Dim intInner As Integer
Dim intOldOuter As Integer
intOldOuter = UBound(mudtOuter)
ReDim Preserve mudtOuter(intOldOuter + 2) As MyArray
For intOuter = intOldOuter + 1 To UBound(mudtOuter)
ReDim mudtOuter(intOuter).strInner(intOuter) As String
For intInner = 0 To UBound(mudtOuter(intOuter).strInner)
mudtOuter(intOuter).strInner(intInner) = "1"
Next intInner
Next intOuter
End Sub
Private Sub Command2_Click()
'change the dimensions of the middle inner array, and fill the extra elements with "2"
Dim intOuter As Integer
Dim intInner As Integer
Dim intOldInner As Integer
intOuter = UBound(mudtOuter) / 2
intOldInner = UBound(mudtOuter(intOuter).strInner)
ReDim Preserve mudtOuter(intOuter).strInner(intOldInner + 5) As String
For intInner = intOldInner + 1 To UBound(mudtOuter(intOuter).strInner)
mudtOuter(intOuter).strInner(intInner) = "2"
Next intInner
End Sub
Private Sub Form_Click()
'clear the form and print the outer,inner arrays
Dim intOuter As Integer
Dim intInner As Integer
Cls
For intOuter = 0 To UBound(mudtOuter)
For intInner = 0 To UBound(mudtOuter(intOuter).strInner)
Print CStr(intOuter) & "," & CStr(intInner) & " = " & mudtOuter(intOuter).strInner(intInner)
Next intInner
Print "" 'add an empty line between the outer array elements
Next intOuter
End Sub
Private Sub Form_Load()
'init the arrays
Dim intOuter As Integer
Dim intInner As Integer
ReDim mudtOuter(5) As MyArray
For intOuter = 0 To UBound(mudtOuter)
ReDim mudtOuter(intOuter).strInner(intOuter) As String
For intInner = 0 To UBound(mudtOuter(intOuter).strInner)
mudtOuter(intOuter).strInner(intInner) = CStr((intOuter + 1) * (intInner + 1))
Next intInner
Next intOuter
WindowState = vbMaximized
End Sub
Run the project, and click on the form to display the contents of the arrays.
Click on Command1 to enlarge the outer array, and click on the form again to show the results.
Click on Command2 to enlarge an inner array, and click on the form again to show the results.
Be careful though: when you redim the outer array, you also have to redim the inner arrays for all the new elements of the outer array
I stumbled across this question while hitting this road block myself. I ended up writing a piece of code real quick to handle this ReDim Preserve on a new sized array (first or last dimension). Maybe it will help others who face the same issue.
So for the usage, lets say you have your array originally set as MyArray(3,5), and you want to make the dimensions (first too!) larger, lets just say to MyArray(10,20). You would be used to doing something like this right?
ReDim Preserve MyArray(10,20) '<-- Returns Error
But unfortunately that returns an error because you tried to change the size of the first dimension. So with my function, you would just do something like this instead:
MyArray = ReDimPreserve(MyArray,10,20)
Now the array is larger, and the data is preserved. Your ReDim Preserve for a Multi-Dimension array is complete. :)
And last but not least, the miraculous function: ReDimPreserve()
'redim preserve both dimensions for a multidimension array *ONLY
Public Function ReDimPreserve(aArrayToPreserve,nNewFirstUBound,nNewLastUBound)
ReDimPreserve = False
'check if its in array first
If IsArray(aArrayToPreserve) Then
'create new array
ReDim aPreservedArray(nNewFirstUBound,nNewLastUBound)
'get old lBound/uBound
nOldFirstUBound = uBound(aArrayToPreserve,1)
nOldLastUBound = uBound(aArrayToPreserve,2)
'loop through first
For nFirst = lBound(aArrayToPreserve,1) to nNewFirstUBound
For nLast = lBound(aArrayToPreserve,2) to nNewLastUBound
'if its in range, then append to new array the same way
If nOldFirstUBound >= nFirst And nOldLastUBound >= nLast Then
aPreservedArray(nFirst,nLast) = aArrayToPreserve(nFirst,nLast)
End If
Next
Next
'return the array redimmed
If IsArray(aPreservedArray) Then ReDimPreserve = aPreservedArray
End If
End Function
I wrote this in like 20 minutes, so there's no guarantees. But if you would like to use or extend it, feel free. I would've thought that someone would've had some code like this up here already, well apparently not. So here ya go fellow gearheads.
This is more compact and respect the intial first position in array and just use the inital bound to add old value.
Public Sub ReDimPreserve(ByRef arr, ByVal size1 As Long, ByVal size2 As Long)
Dim arr2 As Variant
Dim x As Long, y As Long
'Check if it's an array first
If Not IsArray(arr) Then Exit Sub
'create new array with initial start
ReDim arr2(LBound(arr, 1) To size1, LBound(arr, 2) To size2)
'loop through first
For x = LBound(arr, 1) To UBound(arr, 1)
For y = LBound(arr, 2) To UBound(arr, 2)
'if its in range, then append to new array the same way
arr2(x, y) = arr(x, y)
Next
Next
'return byref
arr = arr2
End Sub
I call this sub with this line to resize the first dimension
ReDimPreserve arr2, UBound(arr2, 1) + 1, UBound(arr2, 2)
You can add an other test to verify if the initial size is not upper than new array. In my case it's not necessary
Easiest way to do this in VBA is to create a function that takes in an array, your new amount of rows, and new amount of columns.
Run the below function to copy in all of the old data back to the array after it has been resized.
function dynamic_preserve(array1, num_rows, num_cols)
dim array2 as variant
array2 = array1
reDim array1(1 to num_rows, 1 to num_cols)
for i = lbound(array2, 1) to ubound(array2, 2)
for j = lbound(array2,2) to ubound(array2,2)
array1(i,j) = array2(i,j)
next j
next i
dynamic_preserve = array1
end function
Function Redim2d(ByRef Mtx As Variant, ByVal QtyColumnToAdd As Integer)
ReDim Preserve Mtx(LBound(Mtx, 1) To UBound(Mtx, 1), LBound(Mtx, 2) To UBound(Mtx, 2) + QtyColumnToAdd)
End Function
'Main Code
sub Main ()
Call Redim2d(MtxR8Strat, 1) 'Add one column
end sub
'OR
sub main2()
QtyColumnToAdd = 1 'Add one column
ReDim Preserve Mtx(LBound(Mtx, 1) To UBound(Mtx, 1), LBound(Mtx, 2) To UBound(Mtx, 2) + QtyColumnToAdd)
end sub
If you not want include other function like 'ReDimPreserve' could use temporal matrix for resizing. On based to your code:
Dim n As Integer, m As Integer, i as Long, j as Long
Dim arrTemporal() as Variant
n = 1
m = 0
Dim arrCity() As String
ReDim arrCity(n, m)
n = n + 1
m = m + 1
'VBA automatically adapts the size of the receiving matrix.
arrTemporal = arrCity
ReDim arrCity(n, m)
'Loop for assign values to arrCity
For i = 1 To UBound(arrTemporal , 1)
For j = 1 To UBound(arrTemporal , 2)
arrCity(i, j) = arrTemporal (i, j)
Next
Next
If you not declare of type VBA assume that is Variant.
Dim n as Integer, m As Integer

Resources