Filling a 3D array with 2D arrays in VBA - arrays

I have a set of data where each item has a 2D array of information corresponding to it. I'd like to create a 3D array where the first dimension is the item name and the second and third dimensions are taken up by the info corresponding to the item.
I can read the data for each item into a 2D array, but I can't figure out how to get the 2D array read into the 3D array.
I know the sizes of all the dimensions so I can create an array of fixed size before I begin the reading and writing process.
I'd like to do this by looping only through the names of the items and not looping through every cell of every 2D array.
It is easy to get the 2D arrays read in to an ArrayList but I want to be able to name the items and be able to read these back in to excel and it seems difficult to do with an ArrayList.
The question is: how do I read a 2D selection from excel into a 3D fixed sized array in VBA?

Here is an example of each approach: array of arrays or Dictionary of arrays. The Dictionary approach is considerably easier than the array of arrays if what you want is keyed lookup of values. There might be merits to the array of arrays in other cases.
This is dummy code with no real purpose but to show a couple things: grabbing a single value and an array of values. I am building a 2D array of values by grabbing some Range data which is easy and 2D. From there I build up the arrays of these values and then put them into the relevant data structure. Then I poke at the data structure to get some values out of it.
Array of Arrays approach is shown first (and outputs on the left of the picture).
Sub ArraysOfArrays()
Dim arrA() As Variant
Dim arrB() As Variant
'wire up a 2-D array
arrA = Range("B2:D4").Value
arrB = Range("F2:H4").Value
Dim arrCombo() As Variant
ReDim arrCombo(2, 1) As Variant
'name and give data
arrCombo(0, 0) = "arrA"
arrCombo(1, 0) = arrA
'add more elements
ReDim Preserve arrCombo(2, 2)
arrCombo(0, 1) = "arrB"
arrCombo(1, 1) = arrB
'output a single result
'cell(2,2) of arrA
Range("B6") = arrCombo(1, 0)(2, 2)
Dim str_search As String
str_search = "arrB"
'iterate through and output arrB to cells
Dim i As Integer
For i = LBound(arrCombo, 1) To UBound(arrCombo, 1)
If arrCombo(0, i) = str_search Then
Range("B8").Resize(3, 3).Value = arrCombo(1, i)
End If
Next i
End Sub
Couple key points here:
You can only expand the array using ReDim. ReDim is very particular that you only change the last dimension of the array when used with Preserve. Since I need one of them to track the number of entries, I do that in the second index which is... unnatural. If you know the size in advance, this painful step is skipped.
My final array is a 2xN array where the 2 contains a name and a YxZ array of data.
In order to find a given array in the mix, you have to iterate through them all.
Dictionary of Arrays is far less code and more elegant. Be sure to add the reference Tools->References in the VBA editor to Microsoft Scripting Runtime.
Sub DictionaryOfArrays()
Dim dict As New Scripting.Dictionary
'wire up a 2-D array
arrA = Range("B2:D4").Value
arrB = Range("F2:H4").Value
dict.Add "arrA", arrA
dict.Add "arrB", arrB
'get a single value
Range("F6") = dict("arrB")(2, 2)
'get a array of values
Range("F8").Resize(3, 3) = dict("arrA")
End Sub
Picture of input data and results
Data to copy if you want it (paste in B1)
a b
1 2 3 10 11 12
4 5 6 13 14 15
7 8 9 16 17 18

Related

Jagged Array to 2D Array

i had created a jagged array of non-contigeous columns from a range (excluding any blank or zero-filled columns). now i want to join the different columns in the jagged array and convert it to a 2-Dimensional array that we get if we pass a range to a variant. i am unable to pull the column names using INDEX or TRANSPOSE to populate XValues, Values and Series.Name of a 100%StackedBarChart, hence the need.
here are the screenshots:
JAGGED ARRAY:
RANGE ARRAY:
as you can see in the jagged array, each column cells are shown outermost and row cells 1 to 5 are shown innermost, whereas in range 2d-array, it is viceversa. the columns and rows are of same size viz., 5 rows and 2 columns.
how do i convert back the jagged array to a normal range type rectangular 2D-array? Can anyone help me quickly?
Thanks.
On Florent's idea, i actually wrote my own Jagged2Rectconverter:
Dim tarr
ReDim tarr(1 To UBound(TempArr(1), 1), 1 To UBound(TempArr, 1))
For c = LBound(TempArr, 1) To UBound(TempArr, 1)
For r = LBound(TempArr(c), 1) To UBound(TempArr(c), 1)
tarr(r, c) = TempArr(c)(r, 1)
Next r
Next c
now tarr() gives me the same Rect array:
any quicker way of doing this without looping? :)

How to reference an array and write to another array with more than one column per iteration

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.

Excel VBA: Replace first row by an array

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

Vb.Net Sort 3x One Dimensional Arrays

I have 3 one dimensional arrays.
Each contains information that corresponds to the other 2 arrays.
e.g Array 1 contains a customer first name
Array 2 contains a customer last name
Array 3 contains the customer phone number.
This is not my actual example but is easiest to explain.
How do I sort all three arrays so that they are sorted in order by say customer last name.
If Mr Smith is sorted and has moved from position 10 to position 5 in the lastname array, I would expect his phone number and first name to also be in position 5 in the respective arrays.
I am dealing with arrays with 10,000's of items so I would like to avoid looping (my current method) as this is incredibly slow.
Hoping to use the array.sort methods.
Can someone help me?
Ok - So I have tried to use a new data Type but am still at a loss how I can instantly filter using this. Below is my sample code which has a couple of issues. If someone can resolve - it would love to learn how you did it.
The purpose of the code is to return an array containing grouped issues.
For simplicity I have assumed in the example that each constant found is an issue.
If an issue is found, combine it with other issues found on that same worksheet.
e.g The number 2 is found in both cells A1 and A2 on sheet 1. The array should return A1:A2.
If the issues are found in A1 on sheet 1 and A2 in sheet 2, two seperate array entries would be returned.
Test File and Code Here
Public Type Issues
ws_Sheet As Integer
rng_Range As String
s_Formula As String
s_Combined As String
d_ItemCount As Double
End Type
Sub IssuesFound()
Dim MyIssues() As Issues
Dim i_SheetCount As Integer
Dim s_Formula As String
Dim rng_Range As Range
Dim d_IssueCounter As Double
Dim s_SearchFor As String
Dim a_TempArray() As Issues
Dim d_InsertCounter As Double
d_IssueCounter = -1
' Loop All Sheets Using A Counter Rather Than For Each
For i_SheetCount = 1 To ActiveWorkbook.Sheets.Count
' Loop all Constants On Worksheet
For Each rng_Range In Sheets(i_SheetCount).Cells.SpecialCells(xlCellTypeConstants, 23)
If d_IssueCounter = -1 Then
' First Time and Issue Is Found, Start Recording In An Array
d_IssueCounter = d_IssueCounter + 1
ReDim MyIssues(0)
MyIssues(0).ws_Sheet = i_SheetCount
MyIssues(0).rng_Range = rng_Range.AddressLocal
MyIssues(0).s_Formula = rng_Range.Value
MyIssues(0).s_Combined = i_SheetCount & "#" & rng_Range.Value
MyIssues(0).d_ItemCount = 0
Else
' Going To Look For Issues Found On The Same Sheet with The Same Constant Value
s_SearchFor = i_SheetCount & "#" & rng_Range.Value
' HELP HERE: Need To Ideally Return Whether The Above Search Term Exists In The Array
' Without looping, and I want to return the position in the array if the item is found
a_TempArray = MyIssues 'Filter(MyIssues.s_Combined, s_SearchFor, True, vbTextCompare)
If IsVarArrayEmpty(a_TempArray) = True Then
' New Issue Found - Increase Counter By + 1
d_IssueCounter = d_IssueCounter + 1
' Increase The Array By 1
ReDim Preserve MyIssues(d_IssueCounter)
' Record The Information About The Constant Found. Sheet Number, Constant, Range, and also a combined string for searching and the array position
MyIssues(0).ws_Sheet = i_SheetCount
MyIssues(0).rng_Range = rng_Range.AddressLocal
MyIssues(0).s_Formula = rng_Range.Value
MyIssues(0).s_Combined = i_SheetCount & "#" & rng_Range.Value
MyIssues(0).d_ItemCount = 0
Else
' Get The Array Position Where Other Issues With The Same Worksheet and Constant are Stored
d_InsertCounter = a_TempArray.d_ItemCount
' Add The New Found Constant To The Range Already Containing The Same Constants on This Worksheet
MyIssues(d_InsertCounter).rng_Range = Union(rng_Range, Range(MyIssues(d_InsertCounter).rng_Range)).AddressLocal
End If
End If
Next
Next
End Sub
Function IsVarArrayEmpty(ByRef anArray As Issues)
Dim i As Integer
On Error Resume Next
i = UBound(anArray, 1)
If Err.Number = 0 Then
IsVarArrayEmpty = False
Else
IsVarArrayEmpty = True
End If
End Function
Sample Test File and Code Here
As suggested, you should not be using concurrent arrays at all. You should be defining a type with three properties and then creating a single array or collection of that type.
To answer your question though, there is no way to sort three arrays in concert but there is a way to sort two. What that means is that you can create a copy of the array that you want to use as keys and then use the copy to sort one of the other arrays and the original to sort the other. Check out the documentation for the Array.Copy overload that takes two arrays as arguments.
That said, copying the array and then sorting twice is a big overhead so you may not gain much, if anything, from this method. Better to just do it the right way in the first place, i.e. use a single array of a complex type rather than concurrent arrays of simple types. It's not 1960 any more, after all.

Can you declare jagged arrays in excel VBA directly?

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!

Resources