It is possible to have an array with no upper bound? - excel

I'm populating an array based on whether or not criteria across a worksheet are met. The thing is, I don't know how long the array is, as it could be larger than the dataset itself (will have duplicates of some entries). What I have is something along the lines of:
Sub ArrayTest
Dim MyArray as Variant
Dim i as Long
Dim index as Long
Dim lastRow As Long
lastRow = Cells(Rows.Count, 1).End(xlUp).row
ReDim myarray(index)
For i = 1 to lastrow
If (something) then
index = index + 1
ReDim Preserve recordArrray(Index)
recordArrray(Index) = range("A" & i)
End If
Next
End Sub
However, once index reaches lastrow, I get a subscript out of range error. I can avoid this by simply replacing the ReDim line with ReDim myarray(1 to 100000000). Obviously, this is not an ideal solution.
Can I create an array without having to define what the upper bound will be before it is created?

Arrays always have to have both an upper and lower bound (though you can change the boundaries dynamically with Redim).
My guess is that the problem you're running into is that the starting value for index is 0 which is outside of the legal bounds of your array. I suspect that this would fix the immediate problem:
Sub ArrayTest
Dim MyArray as Variant
Dim i as Long
dim index as Long
Dim lastRow As Long
lastRow = Cells(Rows.Count, 1).End(xlUp).row
ReDim myarray(1 to lastrow)
For i = 1 to lastrow
If (something) then
index = index + 1
recordArrray(Index) = range("A" & i)
End If
Next
End Sub

Related

Load a variable range into an array

I want to store a range of variable size in an one-dimensional array. The range starts at A2 and goes to the last row of the same column. My approach looks like that. It's flawed.
Option Explicit
Sub Range_to_Array()
Dim i, j, k As Integer
Dim arr1(), arr2(), arr3(), arr4() As Variant
With Worksheets("table1")
arr1() = .Cells(.Range("A2"), .Range("A1").End(xlDown).Row)
End With
End Sub
Please, try the next way. Application.Transpose transforms a 2D array with a column in a 1D type. No iteration needed:
Sub Array1DFromColumnRange()
Dim ws As Worksheet, lastR As Long, arr
Set ws = Worksheets("table1")
lastR = ws.Range("A" & ws.rows.count).End(xlUp).Row
arr = Application.Transpose(ws.Range("A2:A" & lastR).Value)
Debug.Print Join(arr, "|") 'just to visually see the result in Immediate Window (Ctrl + G)...
End Sub
The returned 1D array is 1 based, as the 2D array directly extracted from a range. To transform it in zero based type, can be done without iteration, too:
'change the array type to be zero based:
ReDim Preserve arr(0 To UBound(arr) - 1)
Debug.Print LBound(arr)
Debug.Print Join(arr, "|")
Your problem is that your Range-Definition is wrong.
Cells expect 2 parameters (row and column) to address one cell. This is not what you want, and even if, your parameters would be wrong.
What you need in your case is Range.
Now Range can be called either with one or two parameters.
If you call it with one parameter, this defines the whole range.
Examples: Range("A1") or Range("B2:C5") or Range("B:B")
Whats often used in VBA is something like Range("A1:A" & lastRow)
If you call it with two parameters, those two parameters define the first and last cell of the range.
Examples: Range(Range("A1"), Range("C10")) or Range(Cells(1, 1), Cells(10, 3))
I would advice to define an intermediate variable to save the Range - makes it much easier to debug. Also the row number of the last cell should go into an intermediate variable.
In your case you could use one of the following
Dim r As Range, lastRow As Long
' Get the last row in use (usually its better to go up from the end of the sheet)
lastRow = .Cells(.Rows.Count, 1).End(xlUp).Row
' 2 parameters
Set r = .Range(.Cells(2, 1), .Cells(2, lastRow))
' 1 Parameter, last row is concatenated to the range definition
Set r = .Range("A2:A" & lastRow)
' Use Resize
Set r = .Range("A2").Resize(lastRow-1, 1) ' -1 because you start at row 2
arr1 = r.Value
Try this instead.
Sub Range_to_Array()
Dim i As Integer, j As Integer, k As Integer
Dim arr1() As Variant, arr2() As Variant, arr3() As Variant, arr4() As Variant
Dim myRange As Range
With Worksheets("table1")
arr1 = .Range(.Range("A2"), .Range("A1").End(xlDown)).Value
End With
Debug.Print arr1(1, 1)
End Sub
Also please note that in order to properly declare variables, you need to specify data type for each variable separately.
Dim i, j, k As Integer
actually means
Dim i As Variant, j As Variant, k As Integer

Replace and save remaining string in an array

I want to remove predefined parts of the strings in the following table and save the values in an array. For some reason I get an error stating that I'm outside of the index. The lengths of the strings in the table can vary.
Sub New_1()
Dim i, j, k As Integer
Dim Endings As Variant
k = 0
Endings = Array("/A", "/BB", "/CCC", "/DDDD", "/EEEEE")
Dim ArrayValues() As Variant
With Worksheets("table1")
Dim lastRow As Long: lastRow = .Cells(.Rows.Count, 1).End(xlUp).Row
ReDim ArrayValues(lastRow)
For i = lastRow To 1 Step -1
For j = 0 To UBound(Endings)
ArrayValues(k) = Replace(.Range("A" & i), Endings(j), "")
k = k + 1
Next j
Next i
End With
End Sub
You're getting out of bounds because your ArrValues is filled up after not even 3 iterations of your "i" since you're adding up your k every j iterations
If you want an array of the cleaned up cells do this instead:
Sub New_1()
Dim i As Integer, j As Integer, k As Integer
Dim Endings As Variant
Dim ArrayValues() As Variant
Dim lastRow As Long
Endings = Array("/A", "/BB", "/CCC", "/DDDD", "/EEEEE")
With Worksheets("Blad6")
lastRow = .Cells(.Rows.Count, 1).End(xlUp).Row
ReDim ArrayValues(1 To lastRow) 'Then you don't have an empty ArrayValues(0)
For i = lastRow To 1 Step -1
For j = 0 To UBound(Endings)
If j = 0 Then
ArrayValues(i) = Replace(.Range("A" & i), Endings(j), "")
Else
ArrayValues(i) = Replace(ArrayValues(i), Endings(j), "")
End If
Next j
Next i
'Use Array here
End With
End Sub
If your intent is to create an array in which everything after the / is removed, this might be simpler, using the Split function; and also faster by storing the data to be split in a VBA array, in iterating through that array instead of the worksheet cells.
Option Explicit
Sub New_1()
'in VBA, Long is marginally more efficient than Integer
Dim k As Long, v As Variant
Dim dataArr As Variant
Dim ArrayValues() As Variant
With Worksheets("SHEET7")
'faster to loop through VBA array than worksheet cells
'Note that this will be a 2D array with dimensions starting at 1, not 0
dataArr = Range(.Cells(1, 1), .Cells(.Rows.Count, 1).End(xlUp))
End With
'This might be simpler
ReDim ArrayValues(1 To UBound(dataArr, 1))
k = 0
For Each v In dataArr
k = k + 1
ArrayValues(k) = Split(v, "/")(0)
Next v
End Sub

Forcing a variant to be another type

I have the following code:
Dim lRow As Long
Dim c As Variant
lRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
c = Application.Transpose(ws.Range("A2:A" & lRow).Value2)
As long as lRow is > 2 then c becomes a Variant/Variant(1 to x) i.e. an array of Variant/String with values from column A - this is what I need!
However, sometimes the lRow is 2 - this means that c becomes just a string (instead of an array with one entry) - this messes up code further down the sub.
Is there a way I can use Application.Transpose(ws.Range("A2:A" & lRow).Value2) to produce an actual array instead of a Variant? Or somehow force c to always be the array?
Or do I just need to do if checks on the type and build more logic into the whole thing?
I tried Dim c() As String but that's not what Transpose produces...
You should read the range first into a Range variable and then transpose only if it has at least 2 cells:
Dim lRow As Long
Dim c() As Variant
Dim rng As Range
lRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
Set rng = ws.Range("A2:A" & lRow)
If rng.Count > 1 Then
c = Application.Transpose(rng.Value2)
Else
ReDim c(1 To 1, 1 To 1)
c(1, 1) = rng.Value2
End If
Alternatively, you could use a separate function to get the values from a range into an array:
Private Function RangeToArray(ByVal rng As Range) As Variant()
If rng Is Nothing Then Err.Raise 91, "RangeToArray", "Range not set"
If rng.Areas.Count > 1 Then Err.Raise 5, "RangeToArray", "Multi-area range"
If rng.Count > 1 Then
RangeToArray = rng.Value2
Else
Dim arr(1 To 1, 1 To 1) As Variant
arr(1, 1) = rng.Value2
RangeToArray = arr
End If
End Function
But note that when applying Transpose to a 2-dimensional array of 1 value it actually converts it to a 1-dimensional array:
Dim lRow As Long
Dim c() As Variant
lRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
c = Application.Transpose(RangeToArray(ws.Range("A2:A" & lRow))) 'c is 1-D if range has only 1 cell
So, the first choice is probably better.
Finally, you could use your own version of Transpose. See my array repository
EDIT 1
Use the following method if you only need 1D Arrays. It works with rows and columns.
Private Function OneDRangeTo1DArray(ByVal rng As Range) As Variant()
Const methodName As String = "OneDRangeTo1DArray"
If rng Is Nothing Then
Err.Raise 91, methodName, "Range not set"
ElseIf rng.Areas.Count > 1 Then
Err.Raise 5, methodName, "Multi-area range"
ElseIf rng.Rows.Count > 1 And rng.Columns.Count > 1 Then
Err.Raise 5, methodName, "Expected 1-row or 1-column range"
End If
Dim arr() As Variant
If rng.Count = 1 Then
ReDim arr(1 To 1)
arr(1) = rng.Value2
Else
Dim v As Variant
Dim i As Long
ReDim arr(1 To rng.Count)
i = 0
For Each v In rng.Value2
i = i + 1
arr(i) = v
Next v
End If
OneDRangeTo1DArray = arr
End Function
another possibilty
c = Application.Index(ws.Range("A2:A" & lRow).Value2, Application.Evaluate("transpose(row(1:" & lRow - 1 & "))"), 1)
A Variant IS a TYPE (similar to String, Long, Integer, Byte, Double). However, I am guessing you are trying to force a VARIABLE to be a DIFFERENT type (string?) and as part of an ARRAY?
If so, I think this should work for you. It creates an array starting at 0 with a maximum of the last row less two cells. If you wanted to transpose it, or make it multidimensional, just add another layer.
lRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
ReDim c(lRow - 2) As String
'if you need two dimensioanl you could experiment with this:
'ReDim c(lRow - 2, 0) As String
Dim i As Long
For i = 0 To (lRow - 2)
c(i) = ws.Range("A2").Offset(i, 0).Value2
'OR if two dimenssional
'C(i,0) =
Next i
Because I was only after a 1D array, I ended up just creating a function to reuse based on Christian Buse's answer:
Public Function GetArrayFromVerticalRange(rng As Range) As Variant
If rng.Count > 1 Then
GetArrayFromVerticalRange = Application.Transpose(rng.Value2)
Else
Dim c(0) As Variant: c(0) = rng.Value2
GetArrayFromVerticalRange = c
End If
End Function
Used "Vertical" term to remind me to only pass in single column ranges! And could create a "Horizontal" version to use the transpose of the transpose

Alternative solution for looping through the Range in VBA

Recently I was wondering about the possibility of speeding up the program by switching a part of the code below:
Dim cell as Variant
Dim myValues() as Double
ReDim myValues(myRange.Count - 1)
Dim i as Integer: i = 0
For Each cell in myRange.Cells
myValue(i) = cell.Value
i = i + 1
Next cell
into a loop where I could refer to the value of each cell directly, instead of instantiating a cell object, assigning it a cell value from a given range, and then extracting the cell value.
In my mind, the pseudocode would look something like this:
Dim cellValue as Double
Dim myValues() as Double
ReDim myValues(myRange.Count - 1)
Dim i as Integer: i = 0
For each cellValue in myRange.Cells.Values
myValues(i) = cellValue
i = i + 1
Next cellValue
If my overall concept is wrong from the start, or if you can't get the cells values from Range faster, then there was no question.
Due to the fact that this is my first post, I could have written something incorrectly / in wrong posting convention. Let me know and I'll fix it.
Cheers
As #SJR notes, this is a common way to access data from a range without going cell-by-cell
Dim arr, i as long, rng As Range
Set rng = Activesheet.Range("A1:B20")
arr = rng.Value 'arr is now a 2D array (1 To 20, 1 To 2)
'Note: base is 1 and not the more-typical 0
For i = 1 to ubound(arr,1)
Debug.Print arr(i, 1), arr(i, 2)
Next i
arr(3, 2) = 999
rng.value = arr 'put the data back to the range
If the reason is for getting rid of the 2D array a function like this can be the solution:
Function VectorFromRange(rng As Range) As Variant
Dim arrIn As Variant
arr = rng.value 'Dumping the data from range
Dim i As Long
Dim item As Variant
ReDim arrOut(1 To rng.Cells.Count) As Variant
For Each item In arr
i = i + 1
arrOut(i) = item
Next item
VectorFromRange = arrOut
End Function

Fill dynamic array with the rows of visible cells

I am getting an error of "Subscript out of Range" when I'm trying to add the row value, of visible cells (minus the header) to the array. Below the code:
Dim Rng As Range
Dim r As Range
Dim i as Long
Dim arr() As Long
Set Rng = ActiveSheet.UsedRange.Resize(ActiveSheet.UsedRange.Rows.Count - 1, ActiveSheet.UsedRange.Columns.Count).Offset(1, 0).SpecialCells(xlCellTypeVisible)
i = 0
For Each r In Rng.Rows
'Debug.Print r.Row
arr(i) = r.Row
i = i + 1
Next
Am I forgetting something ?! I'm still new to VBA and more so, to arrays.
This function works fine...
I didn't get how you are calculating the range... but this output is coming out of the range only.. array is totally dynamic
Sub foo()
Dim Rng As Range
Dim r As Range
Dim i As Long
Dim arr() As Variant
Set Rng = ActiveSheet.UsedRange.Resize(ActiveSheet.UsedRange.Rows.Count - 1, ActiveSheet.UsedRange.Columns.Count).Offset(1, 0).SpecialCells(xlCellTypeVisible)
i = 1
For Each r In Rng.Rows
ReDim Preserve arr(i)
arr(i) = r.Row
Debug.Print arr(i)
i = i + 1
Next
End Sub
As already pointed out, you have to define the array. You can give it a fixed size when defining it (Dim arr(2) as Integer).
Dynamic ranges can be made with using ReDim. With Preserve it saves the values in the array when redefining the size. (Note: You can only ReDim the last dimension of an array)
The problem is that although you are declaring the array, you haven't initialised it with a size, so there are no elements in the array hence the subscript out of range.
Your code should read, note the other problem you will have is how you are trying to address the range, I have corrected below:
Dim Rng As Range, r As Range
Dim i as integer
Dim ary() as Long
Set Rng = ActiveSheet.UsedRange.Resize(ActiveSheet.UsedRange.Rows.Count - 1, ActiveSheet.UsedRange.Columns.Count).Offset(1, 0).SpecialCells(xlCellTypeVisible)
Redim ary(Rng.Rows.Count)
i = 0
For Each r In Rng.Rows
'Debug.Print r.Row
arr(i) = CLng(r.Row)
i = i + 1
Next
This is a tested and working example

Resources