Excel Macro Multi Dimension Array Value Deleted after IF function - arrays

I met with some problem with Excel Macro.
I am trying too copy values from various cells of a worksheet into an array for use of comparing with other worksheet's cell value later.
However, I am stuck at the array to store all the value I am trying to assign to it.
Below is the code piece I have done.
Sub singleEntry(suppRow As Integer)
Dim j As Integer
Dim myArray() As Variant
Dim a As Integer
Dim b As Integer
Dim c As Integer
Worksheets("Ind. Supp. Plan Time").Select
Cells(suppRow, "I").Select
For j = 9 To 13
c = j - 8
ReDim myArray(5, 4) As Variant
myArray(c, 1) = c
'ReDim Preserve myArray(5, 4) As Variant
If Cells(suppRow, j).Value = "*" Then
ReDim Preserve myArray(5, 4) As Variant
'myArray(j - 8, 1) = j - 8
myArray(j - 8, 2) = Cells(suppRow, "P").Value
myArray(j - 8, 3) = Cells(suppRow, "Q").Value
myArray(j - 8, 4) = Cells(suppRow, "R").Value
MsgBox "array = {" & myArray(c - 1, 2) & "}"
Else
ReDim Preserve myArray(5, 4) As Variant
myArray(j - 8, 2) = "1"
myArray(j - 8, 3) = "1"
myArray(j - 8, 4) = "1"
MsgBox "array(1,3) = {" & myArray(1, 3) & "}"
End If
Next j
ReDim Preserve myArray(5, 4) As Variant
'For a = 1 To 5
' For b = 1 To 4
' MsgBox "Array = {" & myArray(a, b) & "}"
' Next b
'Next a
End Sub
I put in MsgBox to view the result of executing the code, I am sure the lines are executed as expected.
If I print the value of the array straight away after assign one value to it, the value printed is correct.
However, now I can't solve this problem.
Hopefully anyone know this can give me a help.
Thank you very much!

Not sure why you can't retrieve values. I tested this and it works.
Sub singleEntry(suppRow As Integer)
Dim arrStore(1 To 5, 1 To 4) As Variant, col As Integer, r As Integer, c As Integer
Worksheets("Ind. Supp. Plan Time").Select
For col = 9 To 13
arrStore(col - 8, 1) = col - 8
arrStore(col - 8, 2) = IIf(Cells(suppRow, col) = "*", Cells(suppRow, "P"), 1)
arrStore(col - 8, 3) = IIf(Cells(suppRow, col) = "*", Cells(suppRow, "Q"), 1)
arrStore(col - 8, 4) = IIf(Cells(suppRow, col) = "*", Cells(suppRow, "R"), 1)
Next col
For r = 1 To 5
For c = 1 To 4
Debug.Print arrStore(r, c)
Next c
Next r
End Sub
Points to note:
Given that you always fill the array there is no need to ReDim. It's redundant (and expensive)
I've used the ternary IIF statement to tidy up the code i.e. if "*" then x else 1
I don't think you need the variable c so I've removed it
I've added a simple loop at the end to print out the array (which works for me)
Does this solve it?

Related

Combining Multiple Arrays in VBA

I am currently trying to combine 46 arrays in to a single array. I have scoured the internet, to no prevail and am hoping someone here can help. I did find the below page, but I need to be able to look through each element of the new array in a nested for loop, so using the method below doesn't quite get me to my end goal.
Excel vba - combine multiple arrays into one
Basically, I need to combine my set of 46 arrays in such a way that I can then loop through each element using a nested for loop. ie.
Set of arrays:
myArray1 = (1, 2, 3, 4)
myArray2 = (5, 6, 7)
myArray3 = (8, 9)
myArray4 = (10, 11, 12, 13, 14)
.
.
.
myArray46 = (101, 102, 103)
Combine them to form new array:
myNewArray = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14... 101, 102, 103)
Loop through in nested for loop to check each element against my main array:
For i = LBound(mainArray) to UBound(mainArray)
For j = LBound(myArray) to UBound(myArray)
If mainArray(i) = myArray(j) Then
'do something
End If
Next j
Next i
Any help and/ or guidance with this is greatly appreciated!
Since you write in your comments that your end goal is to create an array of unique elements, you might be best served using a dictionary, where you can test for uniqueness as you add each element to dictionary. Something like:
Option Explicit
Function uniqueArr(ParamArray myArr() As Variant) As Variant()
Dim dict As Object
Dim V As Variant, W As Variant
Dim I As Long
Set dict = CreateObject("Scripting.Dictionary")
For Each V In myArr 'loop through each myArr
For Each W In V 'loop through the contents of each myArr
If Not dict.exists(W) Then dict.Add W, W
Next W
Next V
uniqueArr = dict.keys
End Function
Sub tester()
Dim myArray1, myArray2, myArray3, myArray4, myArray5
myArray1 = Array(1, 2, 3, 4)
myArray2 = Array(5, 6, 7, 8)
myArray3 = Array(9, 10, 11, 12, 13, 14)
myArray4 = Array(15, 16)
myArray5 = Array(1, 3, 25, 100)
Dim mainArray
mainArray = uniqueArr(myArray1, myArray2, myArray3, myArray4, myArray5)
End Sub
If you run Tester, you will see mainArray contains:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
25
100
Using your data this is how to create one array out of many:
Public Sub TestMe()
Dim myA, myB, myC, myD, myE
myA = Array(1, 2, 3, 4)
myB = Array(5, 6, 7)
myC = Array(8, 9)
myD = Array(10, 11, 12, 13, 14)
myE = Array(101, 102, 103)
Dim myCombine As Variant
Dim myNew() As Variant
Dim myElement As Variant
Dim myArr As Variant
Dim cnt As Long
myCombine = Array(myA, myB, myC, myD, myE)
For Each myArr In myCombine
For Each myElement In myArr
ReDim Preserve myNew(cnt)
myNew(cnt) = myElement
cnt = cnt + 1
Next
Next
For cnt = LBound(myNew) To UBound(myNew)
Debug.Print myNew(cnt)
Next cnt
End Sub
The "building" of the new array is facilitated through ReDim Preserve, which keeps the old values in the array whenver the dimension of the array changes. And if you want to do something with these arrays, you may use 3 nested loops (a bit slow) and have some check:
Dim cnt2 As Long
For cnt = LBound(myNew) To UBound(myNew)
For cnt2 = LBound(myCombine) To UBound(myCombine)
For Each myElement In myCombine(cnt2)
If myElement = myNew(cnt) Then
Debug.Print myElement & vbTab & " from " & vbTab & cnt2
End If
Next myElement
Next cnt2
Next cnt
This is what you get on the immediate window:
1 from 0
2 from 0
3 from 0
4 from 0
5 from 1
6 from 1
7 from 1
8 from 2
9 from 2
10 from 3
11 from 3
12 from 3
13 from 3
14 from 3
101 from 4
102 from 4
103 from 4
Alternate 'brick-by-brick' approach.
Option Explicit
Sub combineArrays()
Dim myArray1 As Variant, myArray2 As Variant, myArray3 As Variant
Dim myArray4 As Variant, myArray46 As Variant
ReDim mainArray(0) As Variant
myArray1 = Array(1, 2, 3, 4)
myArray2 = Array(5, 6, 7)
myArray3 = Array(8, 9)
myArray4 = Array(10, 11, 12, 13, 14)
'...
myArray46 = Array(101, 102, 103)
mainArray = buildMainArray(myArray1, mainArray)
mainArray = buildMainArray(myArray2, mainArray)
mainArray = buildMainArray(myArray3, mainArray)
mainArray = buildMainArray(myArray4, mainArray)
mainArray = buildMainArray(myArray46, mainArray)
ReDim Preserve mainArray(UBound(mainArray) - 1)
Debug.Print Join(mainArray, ",")
End Sub
Function buildMainArray(arr As Variant, marr As Variant)
Dim i As Long
For i = LBound(arr) To UBound(arr)
marr(UBound(marr)) = arr(i)
ReDim Preserve marr(UBound(marr) + 1)
Next i
buildMainArray = marr
End Function
The issue with using Redim Preserve to combine arrays is it can be an expensive operation, since you're basically re-creating the array everytime it's called. Since you have 46 arrays you're combining, you may very well be waiting a while.
Instead, you can loop over the arrays to figure out the total number of elements you need, dimension out your master array, then loop over the arrays again to do the actual assignment/merging. Something like this:
' encapsulates code to determine length of an individual array
' note that because arrays can have different LBounds in VBA, we can't simply use
' Ubound to determine array length
Public Function GetArrayLength(anArray As Variant) As Integer
If Not IsArray(anArray) Then
GetArrayLength = -1
Else
GetArrayLength = UBound(anArray) - LBound(anArray) + 1
End If
End Function
Public Function CombineArrays(ParamArray arraysToMerge() As Variant) As Variant
' index for looping over the arraysToMerge array of arrays,
' and then each item in each array
Dim i As Integer, j As Integer
' variable to store where we are in the combined array
Dim combinedArrayIndex As Integer
' variable to hold the number of elements in the final combined array
Dim CombinedArrayLength As Integer
' we don't initialize the array with an array-length until later,
' when we know how long it needs to be.
Dim combinedArray() As Variant
' we have to loop over the arrays twice:
' First, to figure out the total number of elements in the combined array
' second, to actually assign the values
' otherwise, we'd be using Redim Preserve, which can get quite expensive
' because we're creating a new array everytime we use it.
CombinedArrayLength = 0
For i = LBound(arraysToMerge) To UBound(arraysToMerge)
CombinedArrayLength = CombinedArrayLength + GetArrayLength(arraysToMerge(i))
Next i
' now that we know how long the combined array has to be,
' we can properly initialize it.
' you can also use the commented code instead, if you prefer 1-based arrays.
ReDim combinedArray(0 To CombinedArrayLength - 1)
' Redim combinedArray(1 to CombinedArrayLength)
' now that the combinedarray is set up to store all the values in the arrays,
' we can begin actual assignment
combinedArrayIndex = LBound(combinedArray)
For i = LBound(arraysToMerge) To UBound(arraysToMerge)
For j = LBound(arraysToMerge(i)) To UBound(arraysToMerge(i))
combinedArray(combinedArrayIndex) = arraysToMerge(i)(j)
combinedArrayIndex = combinedArrayIndex + 1
Next j
Next i
' assign the function to the master array we've been using
CombineArrays = combinedArray
End Function
To use this function, you'd do something like the following:
Public Sub TestArrayMerge()
Dim myArray1() As Variant
Dim myArray2() As Variant
Dim myArray3() As Variant
Dim myArray4() As Variant
Dim combinedArray As Variant
myArray1 = Array(1, 2, 3, 4)
myArray2 = Array(5, 6, 7)
myArray3 = Array(8, 9)
myArray4 = Array(10, 11, 12, 13, 14)
combinedArray = CombineArrays(myArray1, myArray2, myArray3, myArray4)
If IsArray(combinedArray) Then
Debug.Print Join(combinedArray, ",")
End If
End Sub
Regarding your last bit, that you're using an inner loop to combine the values in your final combined array: Your inner loop doesn't need to start at LBound(myArray). For any value of i, you've already compared it to the elements before it (e.g., when i = 2, it's already been compared to the first element). So you really just need:
For i = LBound(combinedArray) To UBound(combinedArray) - 1
For j = i + 1 To UBound(combinedArray)
' do whatever you need
Next j
Next i
Perhaps this ...
'To determine if a multi-dimension array is allocated (or empty)
'Works for any-dimension arrays, even one-dimension arrays
Public Function isArrayAllocated(ByVal aArray As Variant) As Boolean
On Error Resume Next
isArrayAllocated = IsArray(aArray) And Not IsError(LBound(aArray, 1)) And LBound(aArray, 1) <= UBound(aArray, 1)
Err.Clear: On Error GoTo 0
End Function
'To determine the number of items within any-dimension array
'Returns 0 when array is empty, and -1 if there is an error
Public Function itemsInArray(ByVal aArray As Variant) As Long
Dim item As Variant, UBoundCount As Long
UBoundCount = -1
If IsArray(aArray) Then
UBoundCount = 0
If isArrayAllocated(aArray) Then
For Each item In aArray
UBoundCount = UBoundCount + 1
Next item
End If
End If
itemsInArray = UBoundCount
End Function
'To determine the number of dimensions of an array
'Returns -1 if there is an error
Public Function nbrDimensions(ByVal aArray As Variant) As Long
Dim x As Long, tmpVal As Long
If Not IsArray(aArray) Then
nbrDimensions = -1
Exit Function
End If
On Error GoTo finalDimension
For x = 1 To 65536 'Maximum number of dimensions (size limit) for an array that will work with worksheets under Excel VBA
tmpVal = LBound(aArray, x)
Next x
finalDimension:
nbrDimensions = x - 1
Err.Clear: On Error GoTo 0
End Function
'****************************************************************************************************
' To merge an indefinite number of one-dimension arrays together into a single one-dimension array
' Usage: mergeOneDimArrays(arr1, arr2, arr3, ...)
' Returns an empty array if there is an error
' Option Base 0
'****************************************************************************************************
Public Function mergeOneDimArrays(ParamArray infArrays() As Variant) As Variant
Dim x As Long, y As Long, UBoundCount As Long, newUBoundCount As Long
Dim tmpArr As Variant, allArraysOK As Boolean
UBoundCount = 0
allArraysOK = True
For x = LBound(infArrays) To UBound(infArrays)
If Not IsArray(infArrays(x)) Or Not nbrDimensions(infArrays(x)) = 1 Then
allArraysOK = False
Exit For
End If
UBoundCount = UBoundCount + itemsInArray(infArrays(x))
Next x
If allArraysOK Then
ReDim tmpArr(0 To UBoundCount - 1)
UBoundCount = 0
For x = LBound(infArrays) To UBound(infArrays)
For y = LBound(infArrays(x)) To UBound(infArrays(x))
tmpArr(UBoundCount) = infArrays(x)(y)
UBoundCount = UBoundCount + 1
Next y
Next x
newUBoundCount = itemsInArray(tmpArr)
If newUBoundCount = UBoundCount Then
mergeOneDimArrays = tmpArr
Else
mergeOneDimArrays = Array()
End If
Erase tmpArr
Else
mergeOneDimArrays = Array()
End If
End Function
If you are working with one-dimensional arrays you could use a collection instead. It is much better at handling dynamic sizing.
You can declare a collection and then add each of the elements in the arrays to it. Then you will have one large list with all of the values.
Dim coll As New Collection
coll.Add MyArray(j)
Here is a good to collections introduction:
https://excelmacromastery.com/excel-vba-collections/

How do I fill a dynamic 2D Array?

Why does this:
Dim Arr As Variant
p = 1
For i = 1 To LRow
If Sheets("Data").Range("U" & 4 + i).Value > 0 Then
ReDim Preserve Arr(1 To p, 1 To 2)
Arr(p, 1) = Sheets("Data").Range("U" & 4 + i).Value
Arr(p, 2) = Sheets("Data").Range("N" & 4 + i).Value
p = p + 1
End If
Next
results in "run time error 9 - Subscript out of range" at the ReDim line?
I do not know the number of array rows prior to entering the for loop. The column number should always be 2. Doing the same thing but with an 1D Array works, though!
Any help?
As stated, you can only redim preserve the last dimension.
But you can also use other methods to find the number of "rows" needed and set that prior to rediming the array:
Dim Arr As Variant
p = 1
dim rws as long
rws = Application.WorkSheetFunction.CountIf(Sheets("Data").Range("U5:U" & Lrow+4),">0")
Redim Arr(1 to rws,1 to 2)
For i = 1 To LRow
If Sheets("Data").Range("U" & 4 + i).Value > 0 Then
Arr(p, 1) = Sheets("Data").Range("U" & 4 + i).Value
Arr(p, 2) = Sheets("Data").Range("N" & 4 + i).Value
p = p + 1
End If
Next
If you use ReDim Preserve you can only resize the last dimension of an array.
See here:https://learn.microsoft.com/en-us/dotnet/visual-basic/language-reference/statements/redim-statement
If you are looking for a solution, then you can swap array to be Arr(2,p) as you say column number will always be 2.

vba multiD array to range

I'm having an issue with writing an 4D Array to a range in Excel.
My Array Looks like this:
varArray(0)
- varArray(0)(0) "test01"
- varArray(0)(1) "test02"
- varArray(0)(2) "test03"
- varArray(0)(3) "test04"
varArray(1)
- varArray(1)(0) "test11"
- varArray(1)(1) "test12"
- varArray(1)(2) "test13"
- varArray(1)(3) "test14"
There will be more than only 2 "Items" in the Array in the end but for understanding I displayded 2 of them.
I tried it with transpose but I coudl not Access the subitems
Range("A" & CellIndex) = Application.Transpose(varArray(0,1))
does not work :S
Output should look like this(write in to a range):
A B C D
1 test01 test02 test03 test04
2 test11 test12 test13 test14
Can anyone assist me on this?
You can use Application.Transpose twice. This will output to the worksheet in columns A:D
Sub CreateArray()
Dim varArray As Variant
varArray = Array(Array(1, 2, 3, 4), Array(11, 12, 13, 14))
For i = 0 To 1
ThisWorkbook.Worksheets("Sheet1").Range("A1:D1").Offset(i, 0).Value = Application.Transpose(Application.Transpose(varArray(i)))
Next i
End Sub
Try:
Dim varArray(0 To 1, 0 To 3) As String
varArray(0, 0) = "test01"
varArray(0, 1) = "test02"
varArray(0, 2) = "test03"
varArray(0, 3) = "test04"
varArray(1, 0) = "test11"
varArray(1, 1) = "test12"
varArray(1, 2) = "test13"
varArray(1, 3) = "test14"
Range("A1:D2") = varArray()
Range("F1:G4") = Application.Transpose(varArray())
I think the output you want is simply your array, not your transposed array. However I put the two outputs on the code. Feel free to change the adresses...
Do you want something like this:
Option Explicit
Public Sub TestMe()
Dim varArray As Variant
Dim lCounter As Long
Dim lCounter2 As Long
Dim rngCell As Range
varArray = Array(Array(1, 2, 3, 4), Array(11, 12, 13, 14))
Set rngCell = Cells(1, 1)
For lCounter = LBound(varArray) To UBound(varArray)
For lCounter2 = LBound(varArray(lCounter)) To UBound(varArray(lCounter))
Debug.Print varArray(lCounter)(lCounter2)
rngCell = varArray(lCounter)(lCounter2)
Set rngCell = rngCell.Offset(0, 1)
Next lCounter2
Debug.Print "-----------"
Set rngCell = Cells(rngCell.Row + 1, 1)
Next lCounter
End Sub
The result in the immediate window is this one:
1
2
3
4
-----------
11
12
13
14
-----------
From this output, you can easily come to your desired one.
You're trying to transpose a single item in the array:
Application.Transpose(varArray(0,1))
Also, this array isn't indexed in such a manner. You could have varArray(0)(1), but you don't have varArray(0,1).
Try this:
Dim x as Long
For x = LBound(varArray) To UBound(varArray)
Range("A1").Resize(1, UBound(varArray(x)) + 1).Offset(x) = Application.Transpose(Application.Transpose(varArray(x)))
Next

Add column (as first) with 1 to exsiting Variant Array in VBA

I have a array which have 1 or more columns and now I want to add one more column (consists only of 1), but I don't know how do do that. The situation looks like that:
My code:
Dim X() As Variant
X = Range("A1:C3").Value2
It's is important to put column with 1 as first. Probably I need to use ReDim Preserve but nothing works for me.
I think you have some options, but instead of extending the index of the array and transposing, trying to move the values etc which seems too much of a hassle, I would rather add 1 to the Excel range and then create the array:
Range("B1:D3").Value2 = Range("A1:C3").Value2
Range("A1:A3").Value2 = 1
X = Range("A1:D3").Value2
Resize the Array adding a column to the last dimension
Shift all the data to the right.
Assign 1 to the first position in each row
Sub AddColumnShiftData()
Dim X As Variant
Dim i As Long, j As Long
X = Range("A1:C3").Value2
ReDim Preserve X(1 To 3, 1 To 4)
For i = 1 To UBound(X)
For j = UBound(X, 2) To 2 Step -1
X(i, j) = X(i, j - 1)
Next
X(i, 1) = 1
Next
End Sub
Try matrix multiplication by the identify matrix....Well almost identity matrix. Then add 1 to every element in of the resulting matrix. You can use the Excel's Worksheet function for matrix multiplication.
Almost identity matrix
Dim X As Variant
X = Range("A1:C3").Value2
Dim Y As Variant
n = UBound(X, 2)
m = n + 1
Z = UBound(X, 1)
ReDim Y(1 To n, 1 To m)
'Set All values to zero
For i = 1 To n
For j = 1 To m
Y(i, j) = 0
Next j
Next i
' Set offset diagonal to 1
For i = 1 To n
Y(i, i + 1) = 1
Next i
' Matrix MMult
X = Application.WorksheetFunction.MMult(X, Y)
' Add 1 to the first column
For i = 1 To Z
X(i, 1) = 1
Next i
Alternative via Application.Index()
Just for fun (note that the resulting array is a 1-based 2-dim array):
Sub AddFirstIndexColumn()
Const FIXEDVALUE = 1 ' value to replace in new column 1
'[1] get data
Dim v: v = getExampleData()
'[2] define column array inserting first column (0 or 1) and preserving old values (1,2,3)
v = Application.Index(v, _
Application.Evaluate("row(1:" & UBound(v) & ")"), _
Array(1, 1, 2, 3)) ' columns array where 0 reinserts the first column
' [3] add an current number in the first column
Dim i As Long
For i = LBound(v) To UBound(v): v(i, 1) = FIXEDVALUE: Next i
End Sub
Function getExampleData()
' Method: just for fun a rather unusual way to create a 2-dim array
' Caveat: time-consuming for greater data sets; better to assign a range to a datafield array
Dim v
v = Array(Array(2, 3, 5), Array(3, 8, 9), Array(4, 2, 1))
v = Application.Index(v, 0, 0)
getExampleData = v
End Function
Related links
Some pecularities of `Application.Index()
Insert vertical slices into array

Combining 2D (2-dimensional) arrays

I am using VBA in Excel to consume an XML file and dump specific information into individual tabs. I want to be able to combine 2-dimensional arrays. The arrays have a "known" number of columns but an "unknown" number of rows. Consider the following two arrays:
array1:
a b c
d e f
array2:
1 2 3
4 5 6
How do I combine these to arrays if I want the following result:
array3:
a b c
d e f
1 2 3
4 5 6
And just out of curiosity, how would I code if instead I wanted to add to the right instead of the bottom, like this:
array4:
a b c 1 2 3
d e f 4 5 6
I can't seem to find the answer to this anywhere.
Please keep in mind my example above is rather small, but in reality, I'm trying to do this with approx 100,000 rows of data at once. There are only six columns of data, if that matters.
The goal here is to assemble a large array and then write it to an Excel sheet all in one step because when I do it in pieces the performance is really poor.
If possible, I'd prefer a solution that does not require iteration.
The reason I ask about both ways is that in reality I want to add kind of sequentially. For instance, assume I have four arrays, A, B, C, D.
First, add array A:
A
Then, add array B:
A B
Then, add array C:
A B
C
Then, add array D:
A B
C D
and so forth...
Keep in mind that each of the above arrays would be sized such that they "fit" correctly meaning A and B have the same number of rows, but different number of columns. A and C on the other hand have the same number of columns but a different number of rows. And so on...
I wanted to add a demonstration using Macro Man's code from below. Here is what he provided (I added a bit so readers can just copy/paste):
Option Explicit
Sub Testing()
Dim Array1(0 To 1, 0 To 2) As String
Array1(0, 0) = "a"
Array1(0, 1) = "b"
Array1(0, 2) = "c"
Array1(1, 0) = "d"
Array1(1, 1) = "e"
Array1(1, 2) = "f"
Dim Array2(0 To 1, 0 To 2) As String
Array2(0, 0) = "1"
Array2(0, 1) = "2"
Array2(0, 2) = "3"
Array2(1, 0) = "4"
Array2(1, 1) = "5"
Array2(1, 2) = "6"
Dim i As Long
For i = 1 To 25000
With Range("A" & Rows.Count).End(xlUp).Offset(IIf(IsEmpty([A1]), 0, 1), 0)
.Resize(UBound(Array1, 1) - LBound(Array1, 1) + 1, _
UBound(Array1, 2) - LBound(Array1, 2) + 1).Value = Array1
End With
With Range("A" & Rows.Count).End(xlUp).Offset(IIf(IsEmpty([A1]), 0, 1), 0)
.Resize(UBound(Array2, 1) - LBound(Array2, 1) + 1, _
UBound(Array2, 2) - LBound(Array2, 2) + 1).Value = Array2
End With
Next i
End Sub
When you run the above code, which goes back to the spreadsheet each time to write the small amount of data, this takes a long time to run. On my dual Xeon machine, like 25-30 seconds.
However, if you rewrite and populate the array FIRST, then write to the spreadsheet ONCE, it runs in about one second.
Option Explicit
Sub Testing()
Dim Array1(0 To 99999, 0 To 2) As String
Array1(0, 0) = "a"
Array1(0, 1) = "b"
Array1(0, 2) = "c"
Array1(1, 0) = "d"
Array1(1, 1) = "e"
Array1(1, 2) = "f"
Dim i As Long
For i = 0 To 99999
Array1(i, 0) = "a"
Array1(i, 1) = "b"
Array1(i, 2) = "c"
Next i
With Range("A" & Rows.Count).End(xlUp).Offset(IIf(IsEmpty([A1]), 0, 1), 0)
.Resize(UBound(Array1, 1) - LBound(Array1, 1) + 1, _
UBound(Array1, 2) - LBound(Array1, 2) + 1).Value = Array1
End With
End Sub
I would like to see a solution which does the same thing, except being able to add "chunks" of data instead of individual items. Adding arrays to bigger arrays, ideally. Even better would be if the "parent" array somehow dynamically resized itself.
John Coleman's answer below worked great.
I actually combined a bit of Macro Man's with John's test() subroutine and this dynamically re-sizes the range:
Option Explicit
Sub test()
Dim A As Variant, B As Variant
ReDim A(0 To 1, 0 To 1)
ReDim B(0 To 1, 0 To 1)
A(0, 0) = 1
A(0, 1) = 2
A(1, 0) = 3
A(1, 1) = 4
B(0, 0) = 5
B(0, 1) = 6
B(1, 0) = 7
B(1, 1) = 8
Dim Array1 As Variant
Array1 = Combine(A, B)
With Range("A" & Rows.Count).End(xlUp).Offset(IIf(IsEmpty([A1]), 0, 1), 0)
.Resize(UBound(Array1, 1) - LBound(Array1, 1) + 1, _
UBound(Array1, 2) - LBound(Array1, 2) + 1).Value = Array1
End With
End Sub
Here is a VBA function that can combine two 2-dimensional arrays into a single 2-dimensional array. It can be used either from VBA or as an array-formula directly in Excel. Iteration is unavoidable here in VBA since the language doesn't have primitives for things like concatenating arrays:
Function Combine(A As Variant, B As Variant, Optional stacked As Boolean = True) As Variant
'assumes that A and B are 2-dimensional variant arrays
'if stacked is true then A is placed on top of B
'in this case the number of rows must be the same,
'otherwise they are placed side by side A|B
'in which case the number of columns are the same
'LBound can be anything but is assumed to be
'the same for A and B (in both dimensions)
'False is returned if a clash
Dim lb As Long, m_A As Long, n_A As Long
Dim m_B As Long, n_B As Long
Dim m As Long, n As Long
Dim i As Long, j As Long, k As Long
Dim C As Variant
If TypeName(A) = "Range" Then A = A.Value
If TypeName(B) = "Range" Then B = B.Value
lb = LBound(A, 1)
m_A = UBound(A, 1)
n_A = UBound(A, 2)
m_B = UBound(B, 1)
n_B = UBound(B, 2)
If stacked Then
m = m_A + m_B + 1 - lb
n = n_A
If n_B <> n Then
Combine = False
Exit Function
End If
Else
m = m_A
If m_B <> m Then
Combine = False
Exit Function
End If
n = n_A + n_B + 1 - lb
End If
ReDim C(lb To m, lb To n)
For i = lb To m
For j = lb To n
If stacked Then
If i <= m_A Then
C(i, j) = A(i, j)
Else
C(i, j) = B(lb + i - m_A - 1, j)
End If
Else
If j <= n_A Then
C(i, j) = A(i, j)
Else
C(i, j) = B(i, lb + j - n_A - 1)
End If
End If
Next j
Next i
Combine = C
End Function
I tested it in 4 different ways. First I entered your two example arrays in the spreadsheets and used Combine directly in excel as an array formula:
Here A7:C10 contains the array formula
{=combine(A1:C2,A4:C5)}
and A12:F13 contains the array formula
{=combine(A1:C2,A4:C5,FALSE)}
Then, I ran the following sub:
Sub test()
Dim A As Variant, B As Variant
ReDim A(0 To 1, 0 To 1)
ReDim B(0 To 1, 0 To 1)
A(0, 0) = 1
A(0, 1) = 2
A(1, 0) = 3
A(1, 1) = 4
B(0, 0) = 5
B(0, 1) = 6
B(1, 0) = 7
B(1, 1) = 8
Range("A15:B18").Value = Combine(A, B)
Range("C15:F16").Value = Combine(A, B, False)
End Sub
Output:
If possible, I'd prefer a solution that does not require iteration.
Try this:
Function Combine(m, n)
Dim m1&, m2&, n1&, n2&
m1 = UBound(m, 1): m2 = UBound(m, 2)
n1 = UBound(n, 1): n2 = UBound(n, 2)
With Worksheets.Add
.[a1].Resize(m1, m2) = m
.[a1].Resize(n1, n2).Offset(m1) = n
Combine = .[a1].Resize(m1 + n1, m2)
Application.DisplayAlerts = False
Application.ScreenUpdating = False
.Delete
Application.DisplayAlerts = True
Application.ScreenUpdating = True
End With
End Function
Note: this is just a demo to show proof of concept. Currently it does vertical stacking of two 2d arrays. Simple to modify to also do horizontal stacking.
Note: I'm typically opposed to this sort of thing, but if you think about it, an Excel sheet is analogous to a really big 2d array and while this is indeed a sleghammer approach, it is quick and there is no iteration!
You could try re-sizing the destination to match the array's dimensions. Something along the lines of:
(assuming your arrays are called 'Array1' and 'Array2')...
With Range("A" & Rows.Count).End(xlUp).Offset(IIf(IsEmpty([A1]), 0, 1), 0)
.Resize(UBound(Array1, 1) - LBound(Array1, 1) + 1, _
UBound(Array1, 2) - LBound(Array1, 2) + 1).Value = Array1
End With
With Range("A" & Rows.Count).End(xlUp).Offset(IIf(IsEmpty([A1]), 0, 1), 0)
.Resize(UBound(Array2, 1) - LBound(Array2, 1) + 1, _
UBound(Array2, 2) - LBound(Array2, 2) + 1).Value = Array2
End With

Resources