I have the following code below,
I want to get the entire row not just column 1 of the original array, how would i do this?
Sub Example1()
Dim arrValues() As Variant
Dim lastRow As Long
Dim filteredArray()
Dim lRow As Long
Dim lCount As Long
Dim tempArray()
lastRow = Sheets("Raw Data").UsedRange.Rows(Sheets("Raw Data").UsedRange.Rows.Count).Row
arrValues = Sheets("Raw Data").Range(Cells(2, 1), Cells(lastRow, 21)).Value
' First use a temporary array with just one dimension
ReDim tempArray(1 To UBound(arrValues))
For lCount = 1 To UBound(arrValues)
If arrValues(lCount, 3) = "phone" Then
lRow = lRow + 1
tempArray(lRow) = arrValues(lCount, 1)
End If
Next
' Now we know how large the filteredArray needs to be: copy the found values into it
ReDim filteredArray(1 To lRow, 1 To 1)
For lCount = 1 To lRow
filteredArray(lCount, 1) = tempArray(lCount)
Next
Sheets("L").Range("A2:U" & 1 + lRow) = filteredArray
End Sub
The ReDim statement can add records on-the-fly with the PRESERVE parameter but only into the last rank. This is a problem as the second rank of a two dimensioned array is typically considered the 'columns' while the first are the 'rows'.
The Application.Transpose can flip rows into columns and vise-versa but it has limitations. (see here and here)
A simple function to transpose without these limitations is actually very easy to build. All you really need are two arrays and two nested loops to flip them.
Sub Example1()
Dim arrVALs() As Variant, arrPHONs() As Variant
Dim v As Long, w As Long
With Sheets("Raw Data").Cells(1, 1).CurrentRegion
With .Resize(.Rows.Count - 1, 21).Offset(1, 0)
arrVALs = .Cells.Value
'array dimension check
'Debug.Print LBound(arrVALs, 1) & ":" & UBound(arrVALs, 1)
'Debug.Print LBound(arrVALs, 2) & ":" & UBound(arrVALs, 2)
'Debug.Print Application.CountIf(.Columns(3), "phone") & " phones"
End With
End With
ReDim arrPHONs(1 To UBound(arrVALs, 2), 1 To 1)
For v = LBound(arrVALs, 1) To UBound(arrVALs, 1)
If LCase(arrVALs(v, 3)) = "phone" Then
For w = LBound(arrVALs, 2) To UBound(arrVALs, 2)
arrPHONs(w, UBound(arrPHONs, 2)) = arrVALs(v, w)
Next w
ReDim Preserve arrPHONs(1 To UBound(arrPHONs, 1), _
1 To UBound(arrPHONs, 2) + 1)
End If
Next v
'there is 1 too many in the filtered array
ReDim Preserve arrPHONs(1 To UBound(arrPHONs, 1), _
1 To UBound(arrPHONs, 2) - 1)
'array dimension check
'Debug.Print LBound(arrPHONs, 1) & ":" & UBound(arrPHONs, 1)
'Debug.Print LBound(arrPHONs, 2) & ":" & UBound(arrPHONs, 2)
'Option 1: use built-in Transpose
'Worksheets("L").Range("A2:U" & UBound(arrPHONs, 2) + 1) = Application.Transpose(arrPHONs)
'Option 2: use custom my_2D_Transpose
Worksheets("L").Range("A2:U" & UBound(arrPHONs, 2) + 1) = my_2D_Transpose(arrPHONs)
End Sub
Function my_2D_Transpose(arr As Variant)
Dim a As Long, b As Long, tmp() As Variant
ReDim tmp(1 To UBound(arr, 2), 1 To UBound(arr, 1))
For a = LBound(arr, 1) To UBound(arr, 1)
For b = LBound(arr, 2) To UBound(arr, 2)
tmp(b, a) = Trim(arr(a, b))
Next b
Next a
my_2D_Transpose = tmp
End Function
So if you are in a hurry and the scope of your arrays is such that you will never reach the limits of Application.Transpose then by all means use it. If you cannot safely use transpose then use a custom function.
Related
When I paste my array as such,
Set rngPaste = wksSkillsDist.Cells(iStartRow, iFirstColTotal)
rngPaste.Resize(UBound(arrTotals, 1), UBound(arrTotals, 2)) = arrTotals
I get #N/A values that are outside the bounds of my array. In my array, there are no #N/A values.
This is how I declare my Arrray
With wksSkillsDist
'get last Column
iColLastCategory = .Cells(iStartRow - 1, 2).End(xlToRight).Column
'Create array which the indicies match the cells where values will go
ReDim arrTotals(iStartRow To .Cells(iStartRow, iSkillCodeColumn).End(xlDown).Row, 2 To iColLastCategory) As Variant
End With
Here is an example of how items are added to arrTotals. Basically, check to see if certain strings match. If they match then I increment the corresponding spot in the array:
For iColumn = iFirstColPrimary To iLastColPrimary
If szLevel = "Mastered" Then
If InStr(1, wksSkillsDist.Cells(iHeaderRow - 1, iColumn), "Mastered", vbTextCompare) <> 0 And _
StrComp(wksSkillsDist.Cells(iHeaderRow - 2, iColumn).Text, szELM) = 0 And bMasterMatch = False Then
iHeaderCol = iColumn
bMasterMatch = True
iTotal = iTotal + 1
End If
ElseIf szLevel = "Developing" Then
If InStr(1, wksSkillsDist.Cells(iHeaderRow - 1, iColumn), "Developing", vbTextCompare) <> 0 And _
StrComp(wksSkillsDist.Cells(iHeaderRow - 2, iColumn).Text, szELM) = 0 And bMasterMatch = False Then
iHeaderCol = iColumn
bDevelopingMatch = True
iTotal = iTotal + 1
End If
End If
Next iColumn
If bMasterMatch = True Or bPerformingMatch = True Or bDevelopingMatch = True Then
If iTotal > 1 Then
Debug.Print "ERROR"
End If
arrTotals(iSkillRow, iHeaderCol) = arrTotals(iSkillRow, iHeaderCol) + 1
End If
When I paste my values on the sheet using a Loop like such, I get no #N/A Values
'for first y coordinate to last y coordinate in array
For iRow = LBound(arrTotals, 1) To UBound(arrTotals, 1)
'for first x coordinate to last x coordinate in array
For iColumn = LBound(arrTotals, 2) To UBound(arrTotals, 2)
'Add items to SkillDist worksheet
wksSkillsDist.Cells(iRow, iColumn).Value = arrTotals(iRow, iColumn)
Next iColumn
Next iRow
Why is this happening?
Range Size Larger Than Array Size (#N/A)
A Quick Fix
Your array is not a one-based array i.e. its 'LBounds' are not 1 but iStartRow and 2.
Your code is trying to fit the values of the array into a larger range a number of times i.e. e.g. assuming the number of columns is equal, if you're trying to fit an array of 3 rows into a range of 8 rows, it can't be done. If it were 6 or 9 rows, the array would have been written two or three times respectively.
Of course, you want to fit it one time into the correct range. Study the material following this solution.
rngPaste.Resize(UBound(arrTotals, 1) - LBound(arrTotals, 1) + 1, _
UBound(arrTotals, 2) - LBound(arrTotals, 2) + 1) = arrTotals
Any-Based
The number of rows (1st dimension) of any 2D array is calculated in the following way:
Dim rCount as long: rCount = UBound(Data, 1) - LBound(Data, 1) + 1
Similarly, the number of columns (2nd dimension) of any 2D array is calculated in the following way:
Dim cCount as long: cCount = UBound(Data, 2) - LBound(Data, 2) + 1
One-Based
Conveniently, to write the values of a range to a 2D one-based array, if there are at least two cells, one can simply do:
Dim rg As Range: Set rg = Sheet1.Range("A1:J10")
Dim Data As Variant: Data = rg.Value
Conveniently, using what we learned at the beginning, the number of rows in this 2D one-based array is equal to its UBound (since LBound = 1):
Dim rCount As Long: rCount = Ubound(Data, 1) - 1 + 1 ' or...
rCount = Ubound(Data, 1)
Similarly, the number of columns in this 2D one-based array is equal to its UBound (since LBound = 1):
Dim cCount As Long: cCount = Ubound(Data, 2) - 1 + 1 ' or...
cCount = Ubound(Data, 2)
A Small Study
Copy the following code into a standard module, e.g. Module1, of a new workbook. Play with (modify) the constants.
Note that if you set rStart and cStart to 1, the correct result will show in any case. This isn't covered because it would too much complicate the code.
Option Explicit
Sub RangeVsArray()
Const ProcName As String = "RangeVsArray"
' Imagine these are the results of you 'Range.End property' business.
Const rStart As Long = 6
Const rEnd As Long = 8
Const cStart As Long = 2
Const cEnd As Long = 14
Dim Data As Variant: ReDim Data(rStart To rEnd, cStart To cEnd)
' ... i.e.
'ReDim Data(6 To 8, 2 To 14)
' Populate the array (not relevant).
Dim r As Long, c As Long
For r = LBound(Data, 1) To UBound(Data, 1)
For c = LBound(Data, 2) To UBound(Data, 2)
Data(r, c) = r * c
Next c
Next r
Sheet1.Cells.Clear
Dim dCell As Range: Set dCell = Sheet1.Range("A1")
Dim drg As Range
Dim rCount As Long
Dim cCount As Long
' Wrong:
Set drg = dCell.Resize(UBound(Data, 1), UBound(Data, 2))
drg.Value = Data
Dim msg As Long
msg = MsgBox("This is wrong. Do you want to see the correct result?", _
vbYesNo + vbExclamation, ProcName)
If msg = vbYes Then
drg.Clear
' Correct...
rCount = rEnd - rStart + 1 ' it's not rEnd (Ubound(Data, 1))
cCount = cEnd - cStart + 1 ' it's not cEnd (Ubound(Data, 2))
' ... i.e.:
'Dim rCount As Long: rCount = UBound(Data, 1) - LBound(Data, 1) + 1
'Dim cCount As Long: cCount = UBound(Data, 2) - LBound(Data, 2) + 1
Set drg = dCell.Resize(rCount, cCount)
drg.Value = Data
MsgBox "You are watching the correct result.", vbInformation, ProcName
Else
MsgBox "You are watching the wrong result.", vbInformation, ProcName
End If
End Sub
My goal is to select a column of about 300,000 cells and round each cell's value to two decimal places.
I found that looping an array is far faster than looping through cells.
It is much faster if I have the whole array post its data into the cells after the loop rather than during because again posting any data in a loop takes too much time.
Is there a way to write all the values from the new array ("varArray") after the loop is completed?
Sub RoundedTwoDecimalPlaces()
Dim i As Integer
Dim MyArray() As Variant ' Declare dynamic array.
Dim LastRow As Integer
Dim lStart As Double
Dim lEnd As Double
lStart = Timer
LastRow = Cells(1, Selection.Column).End(xlDown).Row
MyArray = Range("a1:a8").Value2
ReDim MyArray(LastRow) ' Resize to x amount of elements.
For i = 1 To LastRow
MyArray(i) = Round(Cells(i, Selection.Column), 2) ' Initialize array.
Next i
''this is where I can't get my array to post into the cells dynamically.
Selection.Value = MyArray()
''to see the amount of time it takes to finish.
'' My goal is to do 300,000 lines quickly
lEnd = Timer
Debug.Print "Duration = " & (lEnd - lStart) & " seconds"
End Sub
You can get the array directly from the range and then restore the altered values:
Sub RoundedTwoDecimalPlaces()
Dim i As Integer
Dim arr As Variant
Dim lStart As Double
Dim ws As Worksheet, col as Long
Set ws = ActiveSheet
col = Selection.Column
lStart = Timer
With ws.Range(ws.Cells(1, col), ws.Cells(1, col).End(xlDown))
arr = .Value
For i = 1 to Ubound(arr, 1)
arr(i, 1) = Round(arr(i, 1), 2)
Next i
.Value = arr
end with
Debug.Print "Duration = " & (Timer - lStart) & " seconds"
End Sub
Here is how I did it using #Tim Williams Code.
I had to loop it because the array has a max character limit.
Here is the finished code:
Sub loopthrough()
Dim i As Integer
Dim arr As Variant
Dim arr1 As Variant
Dim arr2 As Variant
Dim lStart As Double
Dim ws As Worksheet, col As Long
LastRow = Cells(1, Selection.Column).End(xlDown).Row
Set ws = ActiveSheet
col = Selection.Column
lStart = Timer
If LastRow < 30001 Then
With ws.Range(ws.Cells(1, col), ws.Cells(1, col).End(xlDown))
arr = .Value2
For i = 1 To UBound(arr, 1)
If IsNumeric(arr(i, 1)) Then
arr(i, 1) = Round(arr(i, 1), 2)
Else
arr(i, 1) = arr(i, 1)
End If
Next i
.Value2 = arr
End With
Else ''if selection is more than 30,000 lines.
n = 1
Z = 30000
Do While Z < LastRow
With ws.Range(ws.Cells(n, col), ws.Cells(Z, col))
arr = .Value2
For i = 1 To UBound(arr, 1)
If IsNumeric(arr(i, 1)) Then
arr(i, 1) = Round(arr(i, 1), 2)
Else
arr(i, 1) = arr(i, 1)
End If
Next i
.Value2 = arr
End With
n = n + 30000
Z = Z + 30000
Loop
With ws.Range(ws.Cells(n, col), ws.Cells(n, col).End(xlDown))
arr = .Value2
For i = 1 To UBound(arr, 1)
If IsNumeric(arr(i, 1)) Then
arr(i, 1) = Round(arr(i, 1), 2)
Else
arr(i, 1) = arr(i, 1)
End If
Next i
.Value2 = arr
End With
End If
Debug.Print "Duration = " & (Timer - lStart) & " seconds"
End Sub
I have the following code:
Option Explicit
Dim ArrTest() As Variant
Dim ArrSmall() As Variant
Dim ArrTemp() As Variant
Dim k As Integer
Dim i As Integer
Sub test()
ReDim ArrTest(10, 2)
ReDim ArrSmall(10, 2)
ReDim ArrTemp(10, 1)
ArrTest = Range("A1:B10")
For k = 1 To 2
For i = 1 To 10
ArrTemp(i, 1) = ArrTest(i, k)
ArrSmall(i, k) = WorksheetFunction.Small(ArrTemp, i)
Cells(i, k + 10) = ArrSmall(i, k)
Next i
Next k
End Sub
Range("A1:B10") is an arbitrary range of numbers which should be ordered from small to big according to the WorksheetFunction.Small. With a single column this works perfectly fine. However, when applied as such (with a loop) the function copies values from the original range and the output is wrong.
Please try for yourself in an Excel sheet and tell me what I'm doing wrong or the function is wrong?
Thanks.
EDIT: I have it working with Application.Index which works in this particular example. See:
Sub test()
ReDim ArrTest(10, 2)
ReDim ArrSmall(10, 2)
ReDim ArrTemp(10, 1)
ArrTest = Range("A1:B10")
For k = 1 To 2
For i = 1 To 10
ArrTemp = Application.Index(ArrTest, 0, k)
'ArrTemp(i, 1) = ArrTest(i, 1)
ArrSmall(i, 1) = WorksheetFunction.Small(ArrTemp, i)
Cells(i, k + 10) = ArrSmall(i, 1)
Next i
Next k
End Sub
Which works fine. But when I apply the exact same logic to my original code it gives me the 1004 error: unable to get Small property. I have no clue.
to leave your code as much as it was (first example) works perfect:
Option Explicit
Dim ArrTest() As Variant
Dim ArrSmall() As Variant
Dim ArrTemp() As Variant
Dim k As Integer
Dim i As Integer
Sub test()
ReDim ArrTest(10, 2) '*
ReDim ArrSmall(10, 2)
ReDim ArrTemp(10, 1)
ArrTest = Range("A1:B10")
For k = 1 To 2
For i = 1 To 10
ArrTemp(i, 1) = ArrTest(i, k)
Next i
For i = 1 To 10
ArrSmall(i, k) = WorksheetFunction.Small(ArrTemp, i)
Cells(i, k + 10) = ArrSmall(i, k)
Next i
Next k
End Sub
No errors or whatever at all...
the '* just is not used at all ArrTest = Range("A1:B10") will set automatically all ranges... still the ranges would be to big for the other ranges...
I get exactly the same output like in your second example...
EDIT:
If you want to sort the full range (not every column itself) then you need something like:
Option Explicit
Dim ArrTest() As Variant
Dim ArrSmall() As Variant
Dim ArrTemp() As Variant
Dim k As Integer
Dim i As Integer
Sub test()
ReDim ArrTest(10, 2)
ReDim ArrSmall(10, 2)
ReDim ArrTemp(10, 1)
ArrTest = Range("A1:B10")
For k = 1 To UBound(ArrTest, 2)
For i = 1 To UBound(ArrTest)
ArrSmall(i, k) = WorksheetFunction.Small(ArrTest, i + ((k - 1) * UBound(ArrTest)))
Cells(i, k + 10) = ArrSmall(i, k)
Next i
Next k
End Sub
Still I beleave the first code should do what you desire ;)
The way you are using the ReDim statement is creating too many elements in the arrays. Try using the LBound function and UBound function after setting the worksheet range values into the first variant array.
Option Explicit
Sub test()
Dim ArrTest() As Variant, ArrSmall() As Variant, ArrTemp() As Variant
Dim k As Long, i As Long
ReDim ArrTest(10, 2) '<~~ unnecessary if writing values from the worksheet
Debug.Print LBound(ArrTest, 1) & ":" & UBound(ArrTest, 1)
Debug.Print LBound(ArrTest, 2) & ":" & UBound(ArrTest, 2)
'^^ this results in 0:10, 0:2. Not 1:10, 1:2
ArrTest = Range("A1:B10").Value2 '<~~ make sure you are putting values in
Debug.Print LBound(ArrTest, 1) & ":" & UBound(ArrTest, 1)
Debug.Print LBound(ArrTest, 2) & ":" & UBound(ArrTest, 2)
'^^ this results in 1:10, 1:2.
ReDim ArrSmall(LBound(ArrTest, 1) To UBound(ArrTest, 1), _
LBound(ArrTest, 2) To UBound(ArrTest, 2))
ReDim ArrTemp(LBound(ArrTest, 1) To UBound(ArrTest, 1), 1 To 1)
For k = LBound(ArrTest, 2) To UBound(ArrTest, 2)
For i = LBound(ArrTest, 1) To UBound(ArrTest, 1)
ArrTemp(i, 1) = ArrTest(i, k)
ArrSmall(i, k) = WorksheetFunction.Small(ArrTemp, i)
Cells(i, k + 10) = ArrSmall(i, k)
Next i
Next k
End Sub
It depends on how you write the code.
Sub test()
With Range("A1:B10")
For Each Column In .Columns
x = x + 1
For i = 1 To Column.Cells.Count
Cells(i, 10 + x).Value = Application.Small(Column, i)
Next i
Next Column
End With
End Sub
Edit:
Although this would work with Range my goal is to extract the columns
from an array without having to return to a Sheet. I think this is
limited somehow
Adding Array approach too..
Sub test()
Dim vArr As Variant, varrTemp As Variant
vArr = Application.Transpose(Range("A1:B10").Value)
For i = LBound(vArr, 1) To UBound(vArr, 1)
varrTemp = Application.Index(Application.Transpose(vArr), , i)
For x = LBound(varrTemp) To UBound(varrTemp)
Cells(x, 10 + i).Value = Application.Small(varrTemp, x)
Next x
Next i
End Sub
I want to assign values to arrays from a sheet using loop
I tried using this but gives error "Subscript out of Range"
i=1
With ws
Do While i <= 40
ReDim Preserve WorkID(1 To i)
ReDim Preserve Work(1 To i)
ReDim Preserve ComposerName(1 To i)
WorkID(i) = Range("A" & i + 1).Value
Work(i) = Range("B" & i + 1).Value
ComposerName(i) = Range("C" & i + 1).Value
i = i + 1
Loop
End With
I tried both ways to initialize but none of them worked
Initialize Type 1
Dim WorkID() As Variant
Dim Work() As Variant
Dim ComposerName() As Variant
Initialize Type 2
Dim WorkID(1 to 40) As Variant
Dim Work(1 to 40) As Variant
Dim ComposerName(1 to 40) As Variant
Also I tried without Redim as well like this but nothing worked:
i=1
With ws
Do While i <= 40
WorkID(i) = Range("A" & i + 1).Value
Work(i) = Range("B" & i + 1).Value
ComposerName(i) = Range("C" & i + 1).Value
i = i + 1
Loop
End With
Full Sub here :
Option Explicit
Sub Join()
Dim WorkID() 'Stores the workID from Works Sheet
Dim Work() 'Stores the work from Works Sheet
Dim ComposerName() 'Stores the composer from Works Sheet
Dim ConductorID() 'Stores the ConductorID from Conductors Sheet
Dim ConductorNames() 'Stores Conductor Names from Conductors Sheet
Dim CDWorkID() 'Stores CDWorkID from CD Sheet
Dim CDCondID() 'Stores CDConductor ID from CD Sheet
Dim i, j, k, m As Long
Dim ws, wcon, wcd, wj As Worksheet
Set ws = Sheets("Works")
Set wcon = Sheets("Conductors")
Set wcd = Sheets("CDs")
Set wj = Sheets("Join")
i = j = k = 1 'Initalize
ws.Activate
Do While i <= 40
ReDim Preserve WorkID(1 To i)
ReDim Preserve Work(1 To i)
ReDim Preserve ComposerName(1 To i)
WorkID(i) = Range("A" & i + 1).Value
Work(i) = Range("B" & i + 1).Value
ComposerName(i) = Range("C" & i + 1).Value
i = i + 1
Loop
wcon.Activate
Do While j <= 10
ReDim Preserve ConductorID(1 To j)
ReDim Preserve ConductorNames(1 To j)
ConductorID(j) = Range("A" & j + 1).Value
ConductorNames(j) = Range("B" & j + 1).Value
j = j + 1
Loop
wcd.Activate
Do While k <= 132
ReDim Preserve CDWorkID(1 To k)
ReDim Preserve CDCondID(1 To k)
CDWorkID(k) = Range("A" & k + 1).Value
CDCondID(k) = Range("B" * k + 1).Value
k = k + 1
Loop
wj.Activate
For i = LBound(CDWorkID) To UBound(CDWorkID)
Range("F" & i) = CDWorkID(i)
Next i
End Sub
RedDim Preserve is generally an expensive operation since it involves allocating space for a larger array and moving contents from the old array. It is almost always a bad idea to use it inside of a loop. Instead -- determine ahead of time how big the arrays need to be and ReDim just once. If you don't know ahead of time, make them larger than needed and then use a ReDim Preserve after the loop to trim them down to size. In your case, I would Redim the arrays before entering for loops (or even -- why not Dim them the right size to begin with?). Also -- prefix each range with the appropriate worksheet variable rather than activating each in turn. Something like:
Sub Join()
Dim WorkID() 'Stores the workID from Works Sheet
Dim Work() 'Stores the work from Works Sheet
Dim ComposerName() 'Stores the composer from Works Sheet
Dim ConductorID() 'Stores the ConductorID from Conductors Sheet
Dim ConductorNames() 'Stores Conductor Names from Conductors Sheet
Dim CDWorkID() 'Stores CDWorkID from CD Sheet
Dim CDCondID() 'Stores CDConductor ID from CD Sheet
Dim i As Long
Dim ws, wcon, wcd, wj As Worksheet
Set ws = Sheets("Works")
Set wcon = Sheets("Conductors")
Set wcd = Sheets("CDs")
Set wj = Sheets("Join")
ReDim WorkID(1 To 40)
ReDim Work(1 To 40)
ReDim ComposerName(1 To 40)
For i = 1 To 40
WorkID(i) = ws.Range("A" & i + 1).Value
Work(i) = ws.Range("B" & i + 1).Value
ComposerName(i) = ws.Range("C" & i + 1).Value
Next i
ReDim ConductorID(1 To 10)
ReDim ConductorNames(1 To 10)
For i = 1 To 10
ConductorID(i) = wcon.Range("A" & i + 1).Value
ConductorNames(i) = wcon.Range("B" & i + 1).Value
Next i
ReDim CDWorkID(1 To 132)
ReDim CDCondID(1 To 132)
For i = 1 To 132
CDWorkID(k) = wcd.Range("A" & i + 1).Value
CDCondID(k) = wcd.Range("B" & i + 1).Value
Next i
For i = LBound(CDWorkID) To UBound(CDWorkID)
wj.Range("F" & i) = CDWorkID(i)
Next i
End Sub
Range("B" * k + 1).Value has a typo - you meant Range("B" & k + 1).Value. This makes the range raise an "type" error.
Eliminating this makes the code run without error - I suspect the error message is incorrect.
BTW, there is another pitfall (which does not lead to a runtime error, at least not for the code shown):
Dim i, j, k, m As Long
Dim ws, wcon, wcd, wj As Worksheet
will NOT declare i, j, kas Integer but as Variants. Same for ws, wcon, wcd which are variants and NOT worksheet objects.
Ubound can return the max index value of an array, but in a multidimensional array, how would I specify WHICH dimension I want the max index of?
For example
Dim arr(1 to 4, 1 to 3) As Variant
In this 4x3 array, how would I have Ubound return 4, and how would I have Ubound return 3?
ubound(arr, 1)
and
ubound(arr, 2)
You need to deal with the optional Rank parameter of UBound.
Dim arr(1 To 4, 1 To 3) As Variant
Debug.Print UBound(arr, 1) '◄ returns 4
Debug.Print UBound(arr, 2) '◄ returns 3
More at: UBound Function (Visual Basic)
[This is a late answer addressing the title of the question (since that is what people would encounter when searching) rather than the specifics of OP's question which has already been answered adequately]
Ubound is a bit fragile in that it provides no way to know how many dimensions an array has. You can use error trapping to determine the full layout of an array. The following returns a collection of arrays, one for each dimension. The count property can be used to determine the number of dimensions and their lower and upper bounds can be extracted as needed:
Function Bounds(A As Variant) As Collection
Dim C As New Collection
Dim v As Variant, i As Long
On Error GoTo exit_function
i = 1
Do While True
v = Array(LBound(A, i), UBound(A, i))
C.Add v
i = i + 1
Loop
exit_function:
Set Bounds = C
End Function
Used like this:
Sub test()
Dim i As Long
Dim A(1 To 10, 1 To 5, 4 To 10) As Integer
Dim B(1 To 5) As Variant
Dim C As Variant
Dim sizes As Collection
Set sizes = Bounds(A)
Debug.Print "A has " & sizes.Count & " dimensions:"
For i = 1 To sizes.Count
Debug.Print sizes(i)(0) & " to " & sizes(i)(1)
Next i
Set sizes = Bounds(B)
Debug.Print vbCrLf & "B has " & sizes.Count & " dimensions:"
For i = 1 To sizes.Count
Debug.Print sizes(i)(0) & " to " & sizes(i)(1)
Next i
Set sizes = Bounds(C)
Debug.Print vbCrLf & "C has " & sizes.Count & " dimensions:"
For i = 1 To sizes.Count
Debug.Print sizes(i)(0) & " to " & sizes(i)(1)
Next i
End Sub
Output:
A has 3 dimensions:
1 to 10
1 to 5
4 to 10
B has 1 dimensions:
1 to 5
C has 0 dimensions:
UBound(myArray, 1) returns the number of rows in 2d array
UBound(myArray, 2) returns the number of columns in 2d array
However, let's go 1 step further and assume that you need the last row and last column of range, that has been written as a 2d array. That row (or column) should be converted to a 1d array. E.g. if our 2d array looks like this:
Then running the code below, will give you 2 1D arrays, that are the last column and last row:
Sub PrintMultidimensionalArrayExample()
Dim myRange As Range: Set myRange = Range("a1").CurrentRegion
Dim myArray As Variant: myArray = myRange
Dim lastRowArray As Variant: lastRowArray = GetRowFromMdArray(myArray, UBound(myArray, 1))
Dim lastColumnArray As Variant
lastColumnArray = GetColumnFromMdArray(myArray, UBound(myArray, 2))
End Sub
Function GetColumnFromMdArray(myArray As Variant, myCol As Long) As Variant
'returning a column from multidimensional array
'the returned array is 0-based, but the 0th element is Empty.
Dim i As Long
Dim result As Variant
Dim size As Long: size = UBound(myArray, 1)
ReDim result(size)
For i = LBound(myArray, 1) To UBound(myArray, 1)
result(i) = myArray(i, myCol)
Next
GetColumnFromMdArray = result
End Function
Function GetRowFromMdArray(myArray As Variant, myRow As Long) As Variant
'returning a row from multidimensional array
'the returned array is 0-based, but the 0th element is Empty.
Dim i As Long
Dim result As Variant
Dim size As Long: size = UBound(myArray, 2)
ReDim result(size)
For i = LBound(myArray, 2) To UBound(myArray, 2)
result(i) = myArray(myRow, i)
Next
GetRowFromMdArray = result
End Function
In addition to the already excellent answers, also consider this function to retrieve both the number of dimensions and their bounds, which is similar to John's answer, but works and looks a little differently:
Function sizeOfArray(arr As Variant) As String
Dim str As String
Dim numDim As Integer
numDim = NumberOfArrayDimensions(arr)
str = "Array"
For i = 1 To numDim
str = str & "(" & LBound(arr, i) & " To " & UBound(arr, i)
If Not i = numDim Then
str = str & ", "
Else
str = str & ")"
End If
Next i
sizeOfArray = str
End Function
Private Function NumberOfArrayDimensions(arr As Variant) As Integer
' By Chip Pearson
' http://www.cpearson.com/excel/vbaarrays.htm
Dim Ndx As Integer
Dim Res As Integer
On Error Resume Next
' Loop, increasing the dimension index Ndx, until an error occurs.
' An error will occur when Ndx exceeds the number of dimension
' in the array. Return Ndx - 1.
Do
Ndx = Ndx + 1
Res = UBound(arr, Ndx)
Loop Until Err.Number <> 0
NumberOfArrayDimensions = Ndx - 1
End Function
Example usage:
Sub arrSizeTester()
Dim arr(1 To 2, 3 To 22, 2 To 9, 12 To 18) As Variant
Debug.Print sizeOfArray(arr())
End Sub
And its output:
Array(1 To 2, 3 To 22, 2 To 9, 12 To 18)
Looping D3 ways;
Sub SearchArray()
Dim arr(3, 2) As Variant
arr(0, 0) = "A"
arr(0, 1) = "1"
arr(0, 2) = "w"
arr(1, 0) = "B"
arr(1, 1) = "2"
arr(1, 2) = "x"
arr(2, 0) = "C"
arr(2, 1) = "3"
arr(2, 2) = "y"
arr(3, 0) = "D"
arr(3, 1) = "4"
arr(3, 2) = "z"
Debug.Print "Loop Dimension 1"
For i = 0 To UBound(arr, 1)
Debug.Print "arr(" & i & ", 0) is " & arr(i, 0)
Next i
Debug.Print ""
Debug.Print "Loop Dimension 2"
For j = 0 To UBound(arr, 2)
Debug.Print "arr(0, " & j & ") is " & arr(0, j)
Next j
Debug.Print ""
Debug.Print "Loop Dimension 1 and 2"
For i = 0 To UBound(arr, 1)
For j = 0 To UBound(arr, 2)
Debug.Print "arr(" & i & ", " & j & ") is " & arr(i, j)
Next j
Next i
Debug.Print ""
End Sub