Use a non-public array from another module within a new module - arrays

I am following a tutorial on VBA static arrays. I want to make sure my thought process is correct for something simple that I wish to do.
I have code that picks up all 12 months and places it into a static array of type string. As practice, I wanted to see if it's possible to copy these values onto another location using another module that acts as "pasting" the months at the cursor selection, but not utilizing a public array. I'm new to VBA so perhaps my way of think about coding is flawed and for such a purpose, I should be using a publicly defined array.
Sub PopulateStaticArray()
Dim months(11) As String
Dim ndx As Integer
Dim xrow As Long
ndx = 0
xrow = 2
Do Until Cells(xrow, 1).Value = ""
months(ndx) = Cells(xrow, 1).Value
ndx = ndx + 1
xrow = xrow + 1
Loop
End Sub
What I want to achieve in a redundant fashion. (I'm essentially recreating the array from scratch).
Sub InsertMonthsArray()
Dim counter As Integer
Dim rowNum As Double
Dim colNum As Double
Dim months(11) As String
ActiveCell.Select
rowNum = ActiveCell.Row
colNum = ActiveCell.Column
months(0) = "January"
months(1) = "February"
months(2) = "March"
months(3) = "April"
months(4) = "May"
months(5) = "June"
months(6) = "July"
months(7) = "August"
months(8) = "September"
months(9) = "October"
months(10) = "November"
months(11) = "December"
For counter = 0 To UBound(months, 1)
Cells(rowNum, colNum).Value = months(counter)
rowNum = rowNum + 1
Next counter
End Sub
I read some posts about passing arrays, but I'm not sure if that's achieving what I'm looking for. Of course this is not actual project, but just to improve my understanding of the interaction between modules within VBA for Excel. I don't necessary need any code, but just the general conceptual guidance on if I should be using some other method to achieve this task (i.e. Public defined Array or Functions, etc.) Thank you.

#GSerg mentioned correctly
"You are not creating a static array inside PopulateStaticArray. If you want to pass an already existing array to InsertMonthsArray(), regardless of where it comes from, then yes, that should be Sub InsertMonthsArray(months() as string)"
You need to pass your array - declared at procedure level - as (implicit) ByRef argument, which means that you can assign month names to each array item within the called sub procedure createMonths so that the calling procedure can actually use them, for instance to write them to a vertical range (e.g. starting at cell A7).
Option Explicit ' declaration head of code module
Sub PopulateMonthArray()
'[0] declare array at procedure level
Dim months(11) As String ' equals Dim months(0 to 11)
'Dim months(1 To 12) As String ' alternative: declare a 1-based array
'[1] fill array items with month names passing array as (implicit) ByRef argument
createMonths months ' << call sub createMonths
'[2] write 1-dim array vertically to sheet (transposing output from a "flat" to a 2-dim array)
Sheet1.Range("A7").Resize(Rowsize:=12).Value = Application.Transpose(months)
End Sub
Sub procedure createMonths
As you define a 0-based array months(0 To 11) and months count usually from 1 to 12 I added functionality to allow to create a 1-based array as well.
Sub createMonths(months) ' equals Sub createMonths(ByRef months)
Dim countOffset As Long
countOffset = IIf(LBound(months), 0, 1) ' allow calculation of 0-based AND 1-based arrays
Dim i As Long
For i = LBound(months) To UBound(months) ' loop through each array element
months(i) = Application.Text(DateSerial(0, i + countOffset, 1), "mmmm")
Next i
End Sub
Simple alternative /Edit 2020-05-27
If your intention is, however to make your months array disponible within other procedures without need to declare it in each procedure (neither at a global scope nor within a class definition), you could simply profit from a simple workaround: insert a Property Get usable in standard modules, too - not only available in class modules btw. - But note that you don't preserve ("retain") the array actually, you would rebuild it and possibly in a better readable way.
Public Property Get Months()
Dim tmp(1 To 12) ' I'd prefer a 1-based months array :-)
createMonths tmp ' use the same procedure as above (or rebuild it code)
Months = tmp ' return the Get value
End Property
Sub AnyOtherProcedure()
'no further declaration needed
Debug.Print Months(1) ' ~> January
Debug.Print Join(Months, ", ") ' ~> January, February, ..., December
End Sub

Related

Splitting array values into the correct column

Need some help on sorting the values into the correct column.
I can't seem to figure out how I would return the array values to the proper column in the table.
For the output into column B "Pipe DN" it should return the first split text from the values in "Line number", and for the "Service" column F it should return the 2nd split text from "Line number".
How would I accomplish this? -
If for "Pipe DN" I use Range("B19", Range("B19").Offset(Dimension1 - 1, 1)).Value = StrArray, it will return me the correct values, but the "Service" code is not written on the correct column.
Sub SplitLinesIntoArray()
Dim LineNumber() As Variant
Dim StrArray() As Variant
Dim Dimension1 As Long, Counter As Long
LineNumber = Range("J19", Range("J19").End(xlDown))
Dimension1 = UBound(LineNumber, 1)
ReDim StrArray(1 To Dimension1, 1 To 2)
For Counter = 1 To Dimension1
'Pipe DN
StrArray(Counter, 1) = Split(LineNumber(Counter, 1), "-")(0)
Range("B19", Range("B19").Offset(Dimension1 - 1, 0)).Value = StrArray
'Service Code
StrArray(Counter, 2) = Split(LineNumber(Counter, 1), "-")(1)
Range("F19", Range("F19").Offset(Dimension1 - 1, 0)).Value = StrArray(Counter, 2)
Next Counter
'Range("B19", Range("B19").Offset(Dimension1 - 1, 1)).Value = StrArray
Erase LineNumber
Erase StrArray
End Sub
Basically you start well by analyzing a 2-dim datafield array and assigning resulting string manipulations (Split()) to it.
Results seem to (1) output correctly as for the first array "column" ("Pipe DN", starting in cell B19),
whereas (2) the second column ("Service", F19) repeats the result of the very last split action for each array "row".
This impression has to be qualified:
ad 1) You are doing unnecessary extra work by assigning the entire StrArray to the entire "Pipe DN" column,
repeating this action with each single row iteration. (Note that the StrArray gets only completely filled with the last loop).
ad 2) Basically you assign again with each iteration, but this time you get only the latest split result and fill the entire "Service" column
with the latest result assigned to StrArray(Counter,2). Eventually all items show the last split result instead of the individual LineNumber splittings.
See this abbreviated example for three example items only to understand what is happening
(this SnapShot shows the table results when code is stopped after the 2nd iteration (i.e. after Counter=2):
Immediate help
Sticking to your initial code, I'd omit
Range("B19", Range("B19").Offset(Dimension1 - 1, 0)).Value = StrArray as well as
Range("F19", Range("F19").Offset(Dimension1 - 1, 0)).Value = StrArray(Counter, 2)
within the For..Next loop, but add the following two code lines thereafter:
Range("B19", Range("B19").Offset(Dimension1 - 1, 0)).Value = Application.Index(StrArray, 0, 1)
Range("F19", Range("F19").Offset(Dimension1 - 1, 0)).Value = Application.Index(StrArray, 0, 2)
in order to slice the StrArray into columns and write each column separately to your cell target.
Further note:
Fully qualify your range references to prevent possibly unwanted results as Excel would take the currently active sheet if not referenced explicitly ... and this need not be the targeted one :-;
Using VBA, it's not necessary in standard office situations to clear (Erase) at the end of a procedure to free memory.
Possible alternative avoiding array slicing
You might profit from the the following code, which
fully qualifies your range references (note: unqualified refs invite Excel to take the currently active sheet without request),
uses a jagged array (aka as Array of Arrays) to avoid (multiple) column slicing (as needed in OP)
demonstrates the use of Private Constants on module top (used here for enumerating the sub-arrays within the jagged array
demonstrates a help procedure to provide for a correcty dimensioned jagged array:
Example code
Option Explicit ' declaration head of code module (forching variable declarations)
Private Const LineNum As Long = 0 ' enumerate sub-arrays within jagged array
Private Const Pipe As Long = 1
Private Const Service As Long = 2
Sub SplitLinesIntoJaggedArray()
'I. Set Worksheet object to memory ' fully qualify any range references!
Dim ws As Worksheet ' declare ws as of worksheet object type
Set ws = Tabelle1 ' << use the project's sheet Code(Name)
'set ws = ThisWorkbook.Worksheets("Sheet1") ' or: via a sheet's tabular name (needn't be the same)
With ws ' With .. End With structure, note the following "."-prefixes
'II.Definitions
'a) assign target start cell addresses to array tgt
Dim tgt As Variant
tgt = Split("J19,B19,F19", ",") ' split requires "Dim tgt" without brackets to avoid Error 13
'b) define source range object and set to memory
' Note: tgt(LinNum) equalling tgt(0) equalling "J19"
Dim src As Range
Set src = .Range(tgt(LineNum), .Range(tgt(0)).End(xlDown)) ' showing both enumerations only for demo:-)
Dim CountOfRows As Long: CountOfRows = src.Rows.Count ' count rows in source range
'c) provide for a correctly dimensioned jagged array to hold all 2-dim data arrays (three columns)
Dim JaggedArray() As Variant
BuildJagged JaggedArray, CountOfRows ' << call help procedure BuildJaggedArray
'III.Assign column data to JaggedArray
'a) assign LineNum column as 2-dim datafield to JaggedArray(LineNum)
JaggedArray(LineNum) = src.Value
'b) assign LineNum splits to JaggedArray(Pipe) and JaggedArray(Service)
Dim Counter As Long
For Counter = 1 To CountOfRows
'1. Pipe DN
JaggedArray(Pipe)(Counter, 1) = Split(JaggedArray(LineNum)(Counter, 1), "-")(0)
'2. Service Code
JaggedArray(Service)(Counter, 1) = Split(JaggedArray(LineNum)(Counter, 1), "-")(1)
Next Counter
'IV.Write result columns of jagged array to target addresses
' Note: tgt(Pipe)=tgt(1)="B19", tgt(Service)=tgt(2)="F19"
Dim elem As Long
For elem = Pipe To Service
.Range(tgt(elem)).Resize(CountOfRows, 1) = JaggedArray(elem)
Next
End With
End Sub
*Help procedure BuildJagged
Note that the first procedure argument passes the jagged array By Reference (=default, if not explicitly passed ByVal).
This means that any further actions within the help procedure have an immediate effect on the original array.
Sub BuildJagged(ByRef JaggedArray, ByVal CountOfRows As Long)
'Purpose: provide for correct dimensions of the jagged array passed By Reference
ReDim JaggedArray(LineNum To Service) ' include LineNum as data base (gets dimmed later)
Dim tmp() As Variant
ReDim tmp(1 To CountOfRows, 1 To 1)
Dim i As Long
For i = Pipe To Service ' suffices here to start from 1=Pipe to 2=Service
JaggedArray(i) = tmp
Next i
End Sub
Further link
Error in finding last used cell in Excel VBA

Is it possible to make a sub-array that updates the main array?

Say I have a main array: Arr3D(1 to 4, 1 to 4, 1 to 50) and I want to pass over a single subset to a procedure and have that procedure edit the values. I know I can create a sub-array: Arr(1 to 50) and pass over the values, run the procedure, and then put them back into Arr3D.
But VBA's ability to have parameters passed into procedures "ByRef" has me thinking there might be an ability that I'm not aware of to pass a sub-Array "ByRef".
Like I want to pass over Arr3D(1, 2, 1 to 50) into Sub DoStuff(ByRef Arr1D() as Variant) so that the procedure can directly edit the values of the main array.
This is really all just to avoid having to pass the indexes and because I think it looks cleaner, I know I can just pass over the full array and just plug in D1 = 1, D2 = 2 like Arr3D(D1, D2, x).
Is this possible in VBA and how would I write it? If not, what is the best practice in VBA for running procedures on parts of arrays?
VBasic2008 asked for an example, so I made the thing I'm envisioning:
Const Directory = "\\192.168.2.253\Forms\Ingredient Inventory\Entries\"
Sub Example()
'GETTING ALL DATA FROM INPUT ENTRY FILES
Dim oFolder As Object, oFile As Variant, Files() As Object, file_count As Integer
Dim app As New Excel.Application, wb As Workbook, ws As Worksheet
Dim FileData() As Variant
Dim lastRow As Integer
Set oFolder = CreateObject("Scripting.FileSystemObject").getfolder(Directory)
ReDim Files(oFolder.Files.Count)
For Each oFile In oFolder.Files
If oFile.Name Like "*.xls?" Then
Set Files(file_count) = oFile
file_count = file_count + 1
End If
Next oFile
ReDim Preserve Files(file_count - 1)
ReDim FileData(file_count - 1)
For i = LBound(Files) To UBound(Files)
Set wb = app.Workbooks.Add(Files(i).Path)
Set ws = wb.Worksheets(1)
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
FileData(i) = ws.Cells(1, 1).Resize(lastRow, 49).Value
wb.Close savechanges:=false
Next i
app.Quit
set app = Nothing
'END OF INPUT
'INTERPRET DATA
For i = LBound(FileData, 1) To UBound(FileData, 1)
For j = LBound(FileData, 2) To UBound(FileData, 2)
If FileData(i, j, 4) <> "" And FileData(i, j, 5) <> "" Then
Call InputFormattedDate(FileData(i, j))
End If
'Other Stuff
Next j
Next i
'END OF INTERPRET
'OUTPUT DATA
'Do Stuff
'END OF OUTPUT
End Sub
Sub InputFormattedDate(ByRef ArrRow() As Variant)
'ArrRow is a One Dimenion array from 1 to 49
'I wish ArrRow could be referenced like ArrRow(4) or ArrRow(6) instead of ArrRow(D1,D2,4)
Dim MachineNumber As String
MachineNumber = Replace(ArrRow(4), "#", "")
For i = 6 To 10
'For Seperated Columns on output
If CStr(i) = MachineNumber Then
ArrRow(35 + i) = Text_to_Date(ArrRow(5))
Else
ArrRow(35 + i) = "[BLANK]" 'to overwrite previous column values
End If
Next i
End Sub
If the "main" array contains values, then a slice of that array would be another array containing copies of these values, and the fact that arrays are passed by reference has no bearing or implications whatsoever on this. So no, altering values inside a "sub-array" (which is a separate/distinct array holding copies of the original values) isn't going to affect the content of the "main" array.
Because it's holding values instead of references.
You could make it hold references instead, and then you'd have a separate/distinct array holding copies of pointers to the original objects.
Add a new class module and name it WrappedVariant, and make it look like this:
Option Explicit
Private EncapsulatedValue As Variant
'#DefaultMember
Public Property Get Value() As Variant
Value = EncapsulatedValue
End Property
Public Property Let Value(ByVal RHS As Variant)
EncapsulatedValue = RHS
End Property
Where '#DefaultMember is a Rubberduck annotation that controls the value of the procedure's hidden VB_UserMemId attribute, making it 0 (thus making the member the class' default member; you don't need Rubberduck to do this, it's just much, much simpler with).
Now you can do this:
Dim v As WrappedVariant
Set v = New WrappedVariant
v.Value = 42 'or just v = 42 for an implicit default member call
Debug.Print v.Value 'or just Debug.Print v for an implicit default member call
And then v can be stored in a 3D array that's later sliced into a smaller 2D array, and while that will be a completely unrelated separate array, the object pointers in it will still be pointing to the WrappedVariant object instances, each encapsulating their respective value: if you modify one specific instance using any copy of the pointer, it modifies the single object being pointed to no matter which array the pointer came from.
Remember to use the Set keyword to explicitly assign the reference - omitting Set (or using an explicit Let) would coerce the object into its value through an implicit default member call:
Dim array1(1 To 1)
Set array1(1) = v
Dim array2(1 To 1)
Set array2(1) = v
array1(1).Value = 25 '<~ mutate the encapsulated state from either pointer
Debug.Print array1(1), array2(1) '<~ prints 25 twice
That said, you'll still need to implement your own array-slicing nested loops.

Creating an Array from a Range in VBA

I'm having a seemingly basic problem but can't find any resources addressing it.
Simply put, I just want to load the contents of a Range of cells (all one column) into an Array.
I am able to accomplish this by means of
DirArray = Array(Range("A1"), Range("A2"))
But for some reason, I cannot create the array when expressed this way:
DirArray = Array(Range("A1:A2"))
My real Range is much longer (and may vary in length), so I don't want to have to individually enumerate the cells this way. Can anyone tell me how to properly load a whole Range into an Array?
With the latter code:
MsgBox UBound(DirArray, 1)
And
MsgBox UBound(DirArray)
Return 0, whereas with the former they return 1.
Just define the variable as a variant, and make them equal:
Dim DirArray As Variant
DirArray = Range("a1:a5").Value
No need for the Array command.
If we do it just like this:
Dim myArr as Variant
myArr = Range("A1:A10")
the new array will be with two dimensions. Which is not always somehow comfortable to work with:
To get away of the two dimensions, when getting a single column to array, we may use the built-in Excel function “Transpose”. With it, the data becomes in one dimension:
If we have the data in a row, a single transpose will not do the job. We need to use the Transpose function twice:
Note: As you see from the screenshots, when generated this way, arrays start with 1, not with 0. Just be a bit careful.
Edit June.2021:
In newer versions of Excel, the function is: Application.WorksheetFunction.Transpose()
Using Value2 gives a performance benefit. As per Charles Williams blog
Range.Value2 works the same way as Range.Value, except that it does not check the cell format and convert to Date or Currency. And thats probably why its faster than .Value when retrieving numbers.
So
DirArray = [a1:a5].Value2
Bonus Reading
Range.Value: Returns or sets a Variant value that represents the value of the specified range.
Range.Value2: The only difference between this property and the Value property is that the Value2 property doesn't use the Currency and Date data types.
This function returns an array regardless of the size of the range.
Ranges will return an array unless the range is only 1 cell and then it returns a single value instead. This function will turn the single value into an array (1 based, the same as the array's returned by ranges)
This answer improves on previous answers as it will return an array from a range no matter what the size. It is also more efficient that other answers as it will return the array generated by the range if possible. Works with single dimension and multi-dimensional arrays
The function works by trying to find the upper bounds of the array. If that fails then it must be a single value so we'll create an array and assign the value to it.
Public Function RangeToArray(inputRange As Range) As Variant()
Dim size As Integer
Dim inputValue As Variant, outputArray() As Variant
' inputValue will either be an variant array for ranges with more than 1 cell
' or a single variant value for range will only 1 cell
inputValue = inputRange
On Error Resume Next
size = UBound(inputValue)
If Err.Number = 0 Then
RangeToArray = inputValue
Else
On Error GoTo 0
ReDim outputArray(1 To 1, 1 to 1)
outputArray(1,1) = inputValue
RangeToArray = outputArray
End If
On Error GoTo 0
End Function
In addition to solutions proposed, and in case you have a 1D range to 1D array, i prefer to process it through a function like below. The reason is simple: If for any reason your range is reduced to 1 element range, as far as i know the command Range().Value will not return a variant array but just a variant and you will not be able to assign a variant variable to a variant array (previously declared).
I had to convert a variable size range to a double array, and when the range was of 1 cell size, i was not able to use a construct like range().value so i proceed with a function like below.
Public Function Rng2Array(inputRange As Range) As Double()
Dim out() As Double
ReDim out(inputRange.Columns.Count - 1)
Dim cell As Range
Dim i As Long
For i = 0 To inputRange.Columns.Count - 1
out(i) = inputRange(1, i + 1) 'loop over a range "row"
Next
Rng2Array = out
End Function
I'm another vote for iterating through the cells in the range. Unless somebody has found a workaround, my experience trying to assign the range directly to a Variant has been that it works fine (albeit returning a 2-dimensional array when I really only need 1D) except if my range has multiple areas, like for example, when I want just the visible cells in a column of a filtered table, or if I have ctrl-selected different blocks of cells on a sheet.
Iterating through all the cells in the range with a for..each loop always produces the results I expect.
Public Function RangeToArray(ByRef myRange As Range)
Dim i As Long
Dim individualCell As Range
ReDim myArray(myRange.Count - 1)
For Each individualCell In myRange
myArray(i) = individualCell.Text ' or maybe .Value
i = i + 1
Next
RangeToArray = myArray
End Function
I wanted to add this as a comment to Paolo's answer since it's pretty similar but I am a newbie and don't have enough reputation, so here's another slightly different answer.
Adding to #Vityata 's answer, below is the function I use to convert a row / column vector in a 1D array:
Function convertVecToArr(ByVal rng As Range) As Variant
'convert two dimension array into a one dimension array
Dim arr() As Variant, slicedArr() As Variant
arr = rng.value 'arr = rng works too (https://bettersolutions.com/excel/cells-ranges/vba-working-with-arrays.htm)
If UBound(arr, 1) > UBound(arr, 2) Then
slicedArr = Application.WorksheetFunction.Transpose(arr)
Else
slicedArr = Application.WorksheetFunction.index(arr, 1, 0) 'If you set row_num or column_num to 0 (zero), Index returns the array of values for the entire column or row, respectively._
'To use values returned as an array, enter the Index function as an array formula in a horizontal range of cells for a row,_
'and in a vertical range of cells for a column.
'https://usefulgyaan.wordpress.com/2013/06/12/vba-trick-of-the-week-slicing-an-array-without-loop-application-index/
End If
convertVecToArr = slicedArr
End Function
Transpose is a great advice.
I have multiple arrays in my app. Some global, some local, some loaded from ranges and some created programatically.
I had numerous problems with dimensioning. Now, with transpose they are all one dimension.
I did have to modify code slightly, because one version runs on Excel 2003 and another (slower) on 2010.
Caution: You will have to Transpose the array again, when saving it to a range.
Using the shape of the Range
Another approach in creating a function for ArrayFromRange would be using the shape and size of the Range to determine how we should structure the array. This way we don't have to load the data into an intermediate array to determine the dimension.
For instance, if the target range is only one cell, then we know we want to return an array with the single value in it Array(target.value).
Below is the complete function that should deal with all cases. Note, this uses the same technique of using the Application.Transpose method to reshape the array.
' Helper function that returns an array from a range with the
' correct dimensions. This fixes the issue of single values
' not returning as an array, and when a 2 dimension array is returned
' when it only has 1 dimension of data.
'
' #author Robert Todar <robert#roberttodar.com>
Public Function ArrayFromRange(ByVal target As Range) As Variant
Select Case True
' Single cell
Case target.Cells.Count = 1
ArrayFromRange = Array(target.Value)
' Single Row
Case target.Rows.Count = 1
ArrayFromRange = Application.Transpose( _
Application.Transpose(target.Value) _
)
' Single Column
Case target.Columns.Count = 1
ArrayFromRange = Application.Transpose(target.Value)
' Multi dimension array
Case Else
ArrayFromRange = target.Value
End Select
End Function
Testing the ArrayFromRange function
As a bonus, here are the tests that I ran to check that this function works.
' #requires {function} ArrayDimensionLength
' #requires {function} ArrayCount
Private Sub testArrayFromRange()
' Setup a new workbook/worksheet for
' adding testing data
Dim testWorkbook As Workbook
Set testWorkbook = Workbooks.Add
Dim ws As Worksheet
Set ws = testWorkbook.Worksheets(1)
' Add sample data for testing.
ws.Range("A1:A2") = Application.Transpose(Array("A1", "A2"))
ws.Range("B1:B2") = Application.Transpose(Array("B1", "B2"))
' This section will run all the tests.
Dim x As Variant
' Single cell
x = ArrayFromRange(ws.Range("A1"))
Debug.Assert ArrayDimensionLength(x) = 1
Debug.Assert ArrayCount(x) = 1
' Single Row
x = ArrayFromRange(ws.Range("A1:B1"))
Debug.Assert ArrayDimensionLength(x) = 1
Debug.Assert ArrayCount(x) = 2
' Single Column
x = ArrayFromRange(ws.Range("A1:A2"))
Debug.Assert ArrayDimensionLength(x) = 1
Debug.Assert ArrayCount(x) = 2
' Multi Column
x = ArrayFromRange(ws.Range("A1:B2"))
Debug.Assert ArrayDimensionLength(x) = 2
Debug.Assert ArrayCount(x) = 4
' Cleanup testing environment
testWorkbook.Close False
' Print result
Debug.Print "testArrayFromRange: PASS"
End Sub
Helper functions for the tests
In my tests I used two helper functions: ArrayCount, and ArrayDimensionLength. These are listed below for reference.
' Returns the length of the dimension of an array
'
' #author Robert Todar <robert#roberttodar.com>
Public Function ArrayDimensionLength(sourceArray As Variant) As Integer
On Error GoTo catch
Do
Dim currentDimension As Long
currentDimension = currentDimension + 1
' `test` is used to see when the
' Ubound throws an error. It is unused
' on purpose.
Dim test As Long
test = UBound(sourceArray, currentDimension)
Loop
catch:
' Need to subtract one because the last
' one errored out.
ArrayDimensionLength = currentDimension - 1
End Function
' Get count of elements in an array regardless of
' the option base. This Looks purely at the size
' of the array, not the contents within them such as
' empty elements.
'
' #author Robert Todar <robert#roberttodar.com>
' #requires {function} ArrayDimensionLength
Public Function ArrayCount(ByVal sourceArray As Variant) As Long
Dim dimensions As Long
dimensions = ArrayDimensionLength(sourceArray)
Select Case dimensions
Case 0
ArrayCount = 0
Case 1
ArrayCount = (UBound(sourceArray, 1) - LBound(sourceArray, 1)) + 1
Case Else
' Need to set arrayCount to 1 otherwise the
' loop will keep multiplying by zero for each
' iteration
ArrayCount = 1
Dim dimension As Long
For dimension = 1 To dimensions
ArrayCount = ArrayCount * _
((UBound(sourceArray, dimension) - LBound(sourceArray, dimension)) + 1)
Next
End Select
End Function

Error Initializing Array *Type Mismatch*

Attempting to utilize two arrays to iterate through a data transfer process by copy and pasting cells from one sheet into a newly created one. The code below is merely responsible for copy and pasting the correct data in the correct order from one sheet to the newly created one. I'm receiving a type mismatch when attempting to initialize the arrays. It occurs on the first array, but I haven't gotten to the second array to test that yet, so it could be wrong as well.
Things to Note: 1) firmLocationColumn is of type long. 2) All the data stored in said arrays are meant to represent column numbers. They are out of order so I needed to store them in the array in the proper order so that it's easier to iterate through them rather than writing the same information over and over again.
Let me know if I missed anything that needs to be explained and i'll edit my question:
Private Sub GetSpecificTradeDetails(ByVal masterListRow As Long, ByVal firmLocationColumn As Long, ByVal newExcelConfirmSheet As Worksheet, ByVal newExcelConfirmSheetLastRow As Long)
Dim tradesMasterListColumnIndexArray() As Long
Dim newExcelConfirmColumnIndexArray() As Long
Dim arrayIndexCounter As Long
'Sets array of columns for loop iteration through data sheet
tradesMasterListColumnIndexArray() = [1,4,firmLocationColumn,(firmLocationColumn - 1),(firmLocationColumn + 3),15,16,10,11,8,19,18,17,(firmLocationColumn + 4),9,6,2]
newExcelConfirmColumnIndexArray() = [1,2,3,4,5,7,8,9,10,11,12,13,14,15,16,17,18]
Select Case firmLocationColumn
Case 25
'Sets confirm direction to "BUY"
newExcelConfirmSheet.Cells((newExcelConfirmSheetLastRow + 1), 6) = "BUY"
Case 27
'Sets confirm direction to "SELL"
newExcelConfirmSheet.Cells((newExcelConfirmSheetLastRow + 1), 6) = "SELL"
End Select
'Transfers trade details between the masterlist and the newly created confirm sheet
With TradesMasterSheet
For arrayIndexCounter = 0 To 17
.Cells(masterListRow, tradesMasterListColumnIndexArray(arrayIndexCounter)).Copy _
Destination:=newExcelConfirmSheet.Cells((newExcelConfirmSheetLastRow + 1), newExcelConfirmColumnIndexArray(arrayIndexCounter))
Next
End With
End Sub
VBA doesn't support initialization of arrays by array literals. It does, however, have an Array() function:
Dim tradesMasterListColumnIndexArray As Variant
Dim newExcelConfirmColumnIndexArray As variant
tradesMasterListColumnIndexArray = Array(1,4,firmLocationColumn,(firmLocationColumn - 1),(firmLocationColumn + 3),15,16,10,11,8,19,18,17,(firmLocationColumn + 4),9,6,2)
newExcelConfirmColumnIndexArray = Array(1,2,3,4,5,7,8,9,10,11,12,13,14,15,16,17,18)

Combining Arrays in VBA

I have a list of customers from last year (in column A) and I have a list of customers from this year (in Column B). I've put the data from these two columns in arrays (using the code below - which is set up as Option Base 1):
'Define our variables and array types'
Sub CustomerArray()
Dim LastArray() As String
Dim CurrentArray() As String
Dim BothArray() As String
Dim LR As Long
Dim i As Integer
'Define LastArray which is customers last year'
LR = Cells(Rows.Count, 1).End(xlUp).Row
ReDim LastArray(LR - 3)
With Range("A1")
For i = 1 To LR - 3
LastArray(i) = .Offset(i, 0)
Next i
End With
'Define CurrentArray which is customers this year'
ReDim CurrentArray(LR - 3)
With Range("B1")
For i = 1 To LR - 3
CurrentArray(i) = .Offset(i, 0)
Next i
End With
End Sub
Now I want to compare/combine the Arrays to show a list of customers who appear in both of the two arrays I just defined (last year and this year). I want to create a third array with the customers who appear for both years (and I want to put that in column D of my excel sheet). I'm getting confused on how to write the code which will compare these two arrays (current year and last year). Will I use a conditional If > statement? Each of the arrays have the customers listed alphabetically.
I appreicate any help you might be able to give me.
Thanks!
You don't need to mess with arrays or loop at all, keep it simple, try something like this:
Sub HTH()
With Range("A1", Cells(Rows.Count, "A").End(xlUp)).Offset(, 3)
.Formula = "=IF(COUNTIF(B:B,A1)>0,A1,"""")"
.Value = .Value
.SpecialCells(xlCellTypeBlanks).Delete
End With
End Sub
OK. I got a little carried away here, but this does what your are asking (you may have to tune it up to suit your specific needs. To use this code, simply call the Sub "Match Customers".
Your original code proposed the use of three arrays. Excel VBA provides some mechanisms to do what you seek which are both easier to use, and possibly more efficient.
I went ahead and broke the process out into more discrete chunks of code. While it seems like more code, you will find that each peice might make more sense, and it is much more maintainable. You can also now re-use the individual functions for other operations if needed.
I also pulled your range and column indexes out into locally defined constants. This way, if the various row or column references ever need to change, you only have to change the value in one place.
It is not necessarily the most efficient way to do this, but is most likely less complicated than using the arrays you originally propose.
I have not tested this exhaustively, but it works in the most basic sense. Let me know if you have questions.
Hope that helps . . .
Option Explicit
'Set your Column indexes as constants, and use the constants in your code.
'This will be much more maintainable in the long run:
Private Const LY_CUSTOMER_COLUMN As Integer = 1
Private Const CY_CUSTOMER_COLUMN As Integer = 2
Private Const MATCHED_CUSTOMER_COLUMN As Integer = 4
Private Const OUTPUT_TARGET As String = "D1"
Private Const LAST_ROW_OFFSET As Integer = -3
'A Function which returns the list of customers from last year
'as a Range object:
Function CustomersLastYear() As Range
Dim LastCell As Range
'Find the last cell in the column:
Set LastCell = Cells(Rows.Count, LY_CUSTOMER_COLUMN).End(xlUp)
'Return the range of cells containing last year's customers:
Set CustomersLastYear = Range(Cells(1, LY_CUSTOMER_COLUMN), LastCell)
End Function
'A Function which returns the list of customers from this year
'as a Range object:
Function CustomersThisYear() As Range
Dim LastCell As Range
'Find the last cell in the column:
Set LastCell = Cells(Rows.Count, CY_CUSTOMER_COLUMN).End(xlUp)
'Return the range of cells containing this year's customers:
Set CustomersThisYear = Range(Cells(1, CY_CUSTOMER_COLUMN), LastCell)
End Function
'A function which returns a range object representing the
'current list of matched customers (Mostly so you can clear it
'before re-populating it with a new set of matches):
Function CurrentMatchedCustomersRange() As Range
Dim LastCell As Range
'Find the last cell in the column:
Set LastCell = Cells(Rows.Count, MATCHED_CUSTOMER_COLUMN).End(xlUp)
'Return the range of cells containing currently matched customers:
Set CurrentMatchedCustomersRange = Range(Cells(1, MATCHED_CUSTOMER_COLUMN), LastCell)
End Function
'A Function which performs a comparison between two ranges
'and returns a Collection containing the matching cells:
Function MatchedCustomers(ByVal LastYearCustomers As Range, ByVal ThisYearCustomers As Range) As Collection
Dim output As Collection
'A variable to iterate over a collection of cell ranges:
Dim CustomerCell As Range
'Initialize the collection object:
Set output = New Collection
'Iterate over the collection of cells containing last year's customers:
For Each CustomerCell In LastYearCustomers.Cells
Dim MatchedCustomer As Range
'Set the variable to reference the current cell object:
Set MatchedCustomer = ThisYearCustomers.Find(CustomerCell.Text)
'Test for a Match:
If Not MatchedCustomer Is Nothing Then
'If found, add to the output collection:
output.Add MatchedCustomer
End If
'Kill the iterator variable for the next iteration:
Set MatchedCustomer = Nothing
Next
'Return a collection of the matches found:
Set MatchedCustomers = output
End Function
Sub MatchCustomers()
Dim LastYearCustomers As Range
Dim ThisYearCustomers As Range
Dim MatchedCustomers As Collection
Dim MatchedCustomer As Range
'Clear out the destination column using the local function:
Set MatchedCustomer = Me.CurrentMatchedCustomersRange
MatchedCustomer.Clear
Set MatchedCustomer = Nothing
'Use local functions to retrieve ranges:
Set LastYearCustomers = Me.CustomersLastYear
Set ThisYearCustomers = Me.CustomersThisYear
'Use local function to preform the matching operation and return a collection
'of cell ranges representing matched customers. Pass the ranges of last year and this year
'customers in as Arguments:
Set MatchedCustomers = Me.MatchedCustomers(LastYearCustomers, ThisYearCustomers)
Dim Destination As Range
'Use the local constant to set the initial output target cell:
Set Destination = Range(OUTPUT_TARGET)
'Itereate over the collection and paste the matches into the output cell:
For Each MatchedCustomer In MatchedCustomers
MatchedCustomer.Copy Destination
'Increment the output row index after each paste operation:
Set Destination = Destination.Offset(1)
Next
End Sub
If you want to compare the two arrays using loops, maybe because you have, for example, picked up all the data into arrays for faster computation rather than interacting with the spreadsheet range object, or you need to compare multiple things from the two arrays to check that the entries match so can't use a .find statement, then this is what you need:
-Two loops, one nested inside the other
-Three counters, one for each array
-One "Exit Loop", "Exit For", "GoTo foundmatch" or similar way of exiting the inner loop
-A "Redim Preserve" of the results array
-An "If" statement
-Finally, one line where you assign the name that appears in both arrays to the results array
This is everything that is needed to write it simply as loops - but doesn't give the fastest or best way to do it (Redim Preserve is not the best..). Constructing it should be easy from this list though: the if statement should either be an x=y type for a general usage, or if x>y if you are really really sure that the list being looped in the inner loop really is sorted alphabetically

Resources