I declared an array in my VBA function that has a dynamic size. As I cannot ReDim the first dimension of a two- or more-dimensional array, can I add a second dimension to a set array?
This is how I dynamically set the size of my array.
Dim nameArray() As String
Dim arrayCount As Long
For i = 1 To 100
ReDim Preserve nameArray(1 to arrayCount)
nameArray(arrayCount) = "Hello World"
arrayCount = arrayCount + 1
Next i
Now I would like to add a second dimension.
ReDim Preserve nameArray(1 To arrayCount, 1 To 5)
doesn't work.
Is there a workaround?
There isn't any built-in way to do this. Just create a new two-dimensional array and transfer the contents of your existing one-dimensional array into the first row of that new array.
This is what this function does:
Function AddDimension(arr() As String, newLBound As Long, NewUBound As Long) As String()
Dim i As Long
Dim arrOut() As String
ReDim arrOut(LBound(arr) To UBound(arr), newLBound To NewUBound)
For i = LBound(arr) To UBound(arr)
arrOut(i, newLBound) = arr(i)
Next i
AddDimension = arrOut
End Function
Example usage:
nameArray = AddDimension(nameArray, 1, 5)
There is one (also works to delete a dimension), but you will have to think in terms of worksheet dimensions...
use transposition
While I highly prefer the previous 'by hand' method from Jean-François Corbett's and I don't like to rely on Excel build-in function (especially this one!), I would just like to clarify another way for future readers coming here:
adding a dimension to a 1d line vector (a row) means transposing it in Excel
Here, nameArray(1 to arrayCount) is a row (index is a column number) and because of it, if you add a dimension it will become a column since 2d arrays are indexed as (row,column). So, you can just do this:
nameArray = Application.Worksheetfunction.Transpose(nameArray) 'transforms the array to nameArray(1 To arrayCount, 1 To 1), so then:
redim preserve nameArray(1 To arrayCount, 1 To 5)
without any other manipulation.
BUT beware of the very confusing Excel's Transpose function (at least it is the case for me!): the advantage here is that it automatically adds a dimension and redimensions the array for you.
It works as expected only because you are using a 'based 1' index '1d array'.
IF this is not the case, all indices will be shifted by 1, (that's how Transpose is build in to be coherent with cells and ranges). That is: if you start with
nameArray(0 to arrayCount)
you will end up with
nameArray(1 to arrayCount + 1, 1 to 5)
with precautions
While I am at it, it may be off-topic and their are many topics about it, but their are other traps in this Transpose function one should never be enough warned about (not to mention it can consume more time and resources than Jean-François Corbett's solution) :
• if you are dealing with '1d column' in an excel 2d array (a column), that is an array:
nameArray(1 to arrayCount, 1 to 1) [*]
and if you make the transpose of it, the column dimension will be "skipped" and the result will be:
nameArray(1 to arrayCount)
It makes sense since you will end up with an Excel row (so why bother with an extra dim?). But I have to say this is not the intuitive behaviour I would expect, which should be more something like nameArray(1 to 1, 1 to arrayCount).
Note that, a contrario, it can be used to delete a dimension and redimension the array automatically from 2d to 1d: redim preserve the last dimension to 1 to 1 and then transpose! This is the resulting array just above.
• But finally all is not lost: suppose you transpose this array of 1 line:
nameArray(0 to 0, 0 to arrayCount)
you correctly get an array of 1 column:
nameArray(1 to arrayCount + 1, 1 to 1)
(well, almost) - and look back at ref [*] now...
Thus, if you are to use this build-in function and if, moreover you need more dimensions or worst, need to compose transpositions, it can become a bit tricky...
For all this or if you simply need to know correct indexing and number of dimensions of your arrays (not only number of columns), I would suggest a very useful function from users John Coleman and Vegard, the post just below.
While all this should appear logical and trivial for people used to work with Excel sheets, it's very not the case when you are more used to matrix manipulations and I think suggesting the use of this Transpose function should come with some precisions.
Related
Hello I have two dimensional array as below in LotusScript.
Counter = 0
While Not (ProcessingViewDoc Is Nothing )
Redim Preserve AllRecrods(Counter,0)
AllRecrods(Counter,0) = ProcessingViewDoc.Test1(0)
Redim Preserve AllRecrods(Counter,1)
AllRecrods(Counter,1) = ProcessingViewDoc.Test2(0)
Redim Preserve AllRecrods(Counter,2)
Set ProcessingViewDoc = ProcessingView.GetNextDocument(ProcessingViewDoc)
Counter = Counter +1
Wend
When It processes next document it does and reaches to counter 1 and second document it gives me error subscription out of range.
Here is global declaration of array.
Dim AllRecrods() As Variant
Here is the line when it gives error when it goes to loop second time.
Redim Preserve AllRecrods(Counter,0)
In addition to Richard's excellent answer, I would suggest a couple of things.
1) Instead of While Not (ProcessingViewDoc Is Nothing) (which contains two negatives, making it harder to read), use Do Until doc Is Nothing. It is much clearer.
2) If you use a list, you don't have to worry about redim of the array. You could make it a list of a custom data type, and if you use the UNID of the document as the key, you can quickly connect the values back to the originating document.
My code would look something like this:
--- Declarations ---
Type recordData
value1 As String
value2 As String
End Type
--- Main Code ---
Dim allRecords List As recordData
Dim unid as String
Do Until ProcessingViewDoc Is Nothing
unid = ProcessingViewDoc.UniqueID
allRecords(unid).value1 = ProcessingViewDoc.Test1(0)
allRecords(unid).value2 = ProcessingViewDoc.Test2(0)
Set ProcessingViewDoc = ProcessingView.GetNextDocument(ProcessingViewDoc)
Loop
You are using ReDim with the Preserve option and changing both of the dimensions. You can't do that.
From the documentation for the ReDim statement:
If Preserve is specified, you can change only the upper bound of the
last array dimension. Attempting to change any other bound results in
an error.
Also, the logic there is screwed up. You're doing three redims on every iteration, with the first one shrinking the second dimension back to zero on every iteration. Even if you weren't changing the first dimension, that would lose the data that you stored in AllRecrods( n ,1) because the preserve option can't keep data in a dimension that you shrink below the size that you've already used!
You should probably consider swapping your two dimensions, reversing them in your assignments, keeping the first dimension constant at 2, and eliminating two of your ReDim Preserve statements. I.e., just do one ReDim Preserve AllRecrods(2,counter) on each iteration of the loop.
This function returns a error 9, why? I already read couple of similar questions here but nothing really explained me well.
I'm really trying to understand why ReDim Preserve doesnt work in this case because from "j+1" onwards no input is made(i already checked my cells), so isnt overwriting nothing.
Heres the code
Function DadosAnoIndenizações()
ReDim dados(1 To 10000, 1 To 2)
j = 0
For i = 5 To 10000
If (IsNumeric(Worksheets(2).Cells(i, 8).Value) And Not IsEmpty(Worksheets(2).Cells(i, 8).Value)) Then
Ano = CInt(Right(Worksheets(2).Cells(i, 8).Value, 4))
SD = Worksheets(2).Cells(i, 11).Value
j = j + 1
dados(j, 1) = Ano
dados(j, 2) = SD
End If
Next i
ReDim Preserve dados(1 To j, 1 To 2)
DadosAnoIndenizações = dados
End Function
Thanks for any help guys
You are trying to change the first dimension of the array. Redim preserve can only change the last dimension of the array, which is why you're getting the error. You can find this information listed MSDN's website: ReDim Statement (Visual Basic), specifically the "Resizing with Preserve" section:
Resizing with Preserve. If you use Preserve, you can resize only the
last dimension of the array. For every other dimension, you must
specify the bound of the existing array.
For example, if your array has only one dimension, you can resize that
dimension and still preserve all the contents of the array, because
you are changing the last and only dimension. However, if your array
has two or more dimensions, you can change the size of only the last
dimension if you use Preserve.
Typically the way around that is to have the first dimension be your 1 to 2 and the second dimension be your 1 to j and then do a Application.Transpose when writing the results to the worksheet.
I am curious if I can copy multiple columns to a new array from an existing array in one iteration of a loop. Suppose we have the following general example:
Array1 contains 10,000 elements in column1, 10,000 elements in column2, and 10,000 elements in column 3, etc.
Let's say that I want a new array generated off that information, only I want only columns 1 and 2 populated. Can I do this by looping only once with a correctly dimensioned target array? For instance:
'Assume TargetArray has already been ReDimmed to the size of Array1 in the code prior
For i=0 to UBound(Array1)
TargetArray(x,1)= Array1(x,1)
TargetArray(x,2)=Array1(x,2)
Next
So can this be done in one step, or do I have to make a loop for each dimension I want to add to the array. Is there any speed savings by doing two operations per loop as stated above (assuming it works).
Thanks for all of your help!
Have you tried just using Range objects? I just made 100 values in columns A and B, and copy them to F and G. Or are you trying to plug values from the first three columns into an equation to give you values for the new two columns?
Sub CopyRange()
Dim Array1 As Range
Dim Array2 As Range
Set Array1 = Range("A1:B100")
Set Array2 = Range("F1:G100")
Array2.Value = Array1.Value
End Sub
Your example should work as what RubberDuck commented.
It is similar in below example which works at my end.
I can't fit it to comments so I have no choice to post it as answer.
Dim TargetArray ' declared as Variant type, not array of variants
ReDim TargetArray(0 To Ubound(Array1, 0), 0 To 1) ' for 2 columns
For i = 0 To Ubound(Array1, 1)
TargetArray(i, 0) = Array1(i, 0)
TargetArray(i, 1) = Array1(i, 1)
Next
Is this close to what you have? If so, then that should work.
I have a first row with 100 cells and I created an Array of Strings, which represent the new row content.
I would like to replace the content of all the first row with the content of my Array in VBA, how can I do that?
Say your array is called myArray, it's enough to do this:
For j = LBound(myArray) To UBound(myArray)
Sheets("your sheet").Cells(1,j+1).Value = myArray(j)
Next j
The functions LBound() and UBound() are respectively returning the first and the last index of your array.
Please note that when writing Cells(1,j+1) I'm assuming two important things:
1) Your start index starts with 0, so I want to start the insertion of the values from the column 1 (j+1 = 0+1 = 1).
2) You want to override the first row (because the row index is equal to 1).
You might want to customize this, for example creating independent indexes - when I say "independent", I mean "not depending on the lower and the upper bound of your array, nor being hard-coded like I did for the "row 1".
You can read and write between a Range and an Array in one line. It is more efficient than using a loop.
Note: The array must be 2 Dimensional to write to a range.
Public Sub ReadToArray()
' Create dynamic array
Dim StudentMarks() As Variant
' Read values into array from 100 cells in row 1
StudentMarks = Sheets("Sheet1").Range("A1:CV1").Value
' Do something with array
' Write the values back to sheet
Sheets("Sheet1").Range("A1:CV1").Value = StudentMarks
End Sub
In my project I work a lot with jagged arrays, i.e. arrays of which the elements are also arrays.
Up until know I only managed to define these arrays like this:
dim subarray(1 to 3) as Integer
dim MyArray(1 to 5) as Variant
subarray(1) = 40
subarray(2) = 50
subarray(3) = 60
MyArray(1) = subarray
But I would like to do something like this:
dim MyArray(1 to 5)(1 to 3) as Variant/Integer
MyArray(1)(1) = 40
The example above doesn't compile. Is there a similar, valid way to declare nested arrays directly?
EDIT: The right term is 'jagged array' not 'nested array'.
EDIT2: Edited the example values, to prevent confusion between indices and values.
There are a variety of ways in VBA to have collections of collections. All of them have benefits and drawbacks.
Multidimensional Arrays
Good:
Simple syntax (only one variable)
Type safety. All elements of a matrix of Integer are known and enforced to be Integers.
Very fast array access
Bad:
If there are large differences in the size of the inner arrays, a matrix will waste some space because there are unused "cells" in the matrix.
You can only change the bounds of the last dimension with ReDim Preserve. So you can't add "columns" to a matrix without clearing all the data.
You declare multidimensional arrays by including multiple bounds separated by commas:
Dim intMatrix(0 to 2, 0 to 4) As Integer
You can dynamically increase the last dimension of a multidimensional array if you first declare the array without any bounds:
Dim intMatrix() As Integer ' Uninitialized dynamic array
ReDim intMatrix(0 to 4, 0 to 2) ' Initialize as a matrix
ReDim Preserve intMatrix(0 to 4, 0 to 3) ' Add another "row" to the matrix, preserving existing data
Jagged Arrays
Good:
Flexible
Bad:
You lose compile-time type safety
They are a bit tricky / messy because of the nested structure
It is awkward and expensive to resize the inner arrays
You can create jagged arrays be declaring an outer array of type Variant(), and assigning other arrays to the elements of the outer array:
Dim outer() As Variant ' Dynamic, so new inner arrays can be added
Dim inner() As Integer ' Dynamic, so new elements can be added
ReDim outer(0 to 3)
ReDim inner(0 to 4)
outer(2) = inner
Lost compile-time type information
All the compiler "knows" about the outer array is that it can contain anything. So the following code will compile:
Set objWorksheet = outer(2)(3)
Although at runtime this will cause an error because the inner array at outer(2) contains Integers, not Worksheet objects.
Awkward to resize
One of the benefits of jagged array is that the inner arrays can be of different sizes. However, you cannot directly resize an inner array. VBA just can't handle the syntax; the following doesn't compile:
ReDim Preserve outer(2)(0 to 5)
In order resize an inner array, you first have to assign the inner array to a separate variable, resize that variable, and then assign it back to the jagged array:
Dim tempInts() As Integer
tempInts = outer(2)
ReDim Preserve tempInts(0 to 5)
outer(2) = tempInts
The reason you have to reassign tempInts back to the outer array is that arrays use by-value semantics in VBA. That means when you assign an array to a variable (as in tempInts = outer(2), you copy the entire array. That can be very expensive if your array is long (say a few thousand elements), and even more expensive if your array contains strings, because every single string must also be copied.
Jagged Collections
Good:
Simple syntax for adding and removing elements
Just as flexible as jagged arrays
Collections use by-reference semantics so assigning is cheap, and you can have multiple references to the same collection object
Bad:
Like jagged arrays, there is no type safety
If you will be adding elements to your inner arrays frequently, it will be a lot easier to use Collection objects instead of arrays. Collections do not enforce the data type of their elements, so this has the same drawbacks of using Variant arrays -- but you must do that to use jagged arrays anyways.
Dim cAnimals As New Collection
' Let's add stats on the Cheetah
Dim cCheetah As New Collection
' Easy to add inner collections to the outer collection. Also, cCheetah refers
' to the same collection object as cAnimals(1).
cAnimals.Add cCheetah
' Easy to add items to inner collection.
' Working directly with the cCheetah collection:
For Each vMeasurment In GetMeasurements("Cheetah")
cCheetah.Add vMeasurement
Next
' Working on the same collection by indexing into the outer object
For i = 1 To cAnimals.Count
For j = 1 To cAnimals(i).Count
cAnimals(i)(j) = cAnimals(i)(j) * dblNormalizingFactor
Next
Next
Array of Arrays:
Dim aa(), ax(), dd, x(), xx(), x2() ' all are " As Variant"
' Array of Arrays - Variant(0 To 2) with 3 Variant(0 To 2) ( 3 Variant/Integer each )
aa = Array( Array(1, 2, 3), Array(4, 5, 6), Array(7, 8, 9) )
aa(0)(0) = 0
' Array of "Excel" arrays - Variant(0 To 2) with 3 Variant(1 To 3) (3 Variant/Integer each)
ax = Array([{1,2,3}], [{4,5,6}], [{7,8,9}])
ax(0)(1) = 0
Another option is Collection of Collections, or Dictionary of Dictionaries:
Set dd = CreateObject("Scripting.Dictionary")
Set dd(2) = CreateObject("Scripting.Dictionary")
dd(2)(4) = 24
Some "Excel" rectangular array examples (because not a VBA type and works in Excel formulas too):
' "row" array starts at 1 - Variant(1 To 3) with 3 Variant/Integer each
x = [{1,2,3}]
x(1) = 0
' "column" array starts at 1, 1 - Variant(1 To 3, 1 To 1)
xx = [{1;2;3}]
xx(1, 1) = 0
' "Excel" rectangular array - Variant(1 To 3, 1 To 3)
x2 = [{1,2,3;4,5,6;7,8,9}]
x2(1, 1) = 0
Stop ' pause to check the types in the Locals window
As Joshua says: there is no specific VBA syntax for declaring jagged arrays directly. But Jagged arrays follow the normal VBA rules for assignment: eg
Dim a as integer
dim v as variant
a=17
v=a
a=19
You don't expect V to now equal 19!