Subscript out of range when passing argument to array - excel

I'm setting up a subroutine to perform matches between two worksheets. The arrays are one dimensional going from the first cell of data to the last, which is held within a variable.
The data in the arrays are not numerical, but if I ReDim them as strings I get a type mismatch in the initialization.
SheetOneLastRow and SheetTwoLastRow are subroutines which find the last row in each sheet to be held in the variables FirstLastRow and SecondLastRow which are declared globally because they are used in other subs.
EDIT 1: The error is on the line:
If search(i) = arr(j) Then
Value of FirstLastRow is 9589 and SecondLastRow is 20750.
The search and arr have only been declared here with ReDim.
Sub Match()
SheetOneLastRow
SheetTwoLastRow
Dim i, j As Integer
ReDim arr(SecondLastRow - 2) As Variant
ReDim search(FirstLastRow - 2) As Variant
search = Range(wksv.Cells(2, 11), wksv.Cells(FirstLastRow, 11))
arr = Range(wkst.Cells(2, 6), wkst.Cells(SecondLastRow, 6))
For i = 2 To FirstLastRow
For j = 2 To SecondLastRow
If search(i-2) = arr(j-2) Then
wkst.Cells(j, 3) = wksv.Cells(i, 3)
End If
Next j
Next i
End Sub

Search() is a 2D array, and the code is using it as a 1D array.
In general, passing range to arrays is not complicated, but there are a few tricks, you should be aware of. First trick - whenever the range is passed like this:
search = Range(wksv.Cells(2, 11), wksv.Cells(FirstLastRow, 11)) it is passed to a 2-dimensional array. See the blue highlighted line at the screenshot:
The problem with the 2-dimensional arrays is that they are of two dimensions. E.g., you should be looking for Search(2,1) instead of Search(2). Or in the code above it should be: If Search(i,1) = arr(j,1) Then
There are probably better ways to solve the problem, e.g. passing the range to a single dimensional array, as in the example here - https://stackoverflow.com/a/52467171/5448626
This is what would happen, if you force the range to be a 1D array:
Sub Match()
Dim i, j As Integer
FirstLastRow = 9589
SecondLastRow = 20750
ReDim arr(SecondLastRow - 2) As Variant
ReDim Search(FirstLastRow - 2) As Variant
With Worksheets(1) 'put wksv
Search = Application.Transpose(.Range(.Cells(2, 11), .Cells(FirstLastRow, 11)))
End With
With Worksheets(2) 'put wkst
arr = Application.Transpose(.Range(.Cells(2, 6), .Cells(SecondLastRow, 6)))
End With
For i = 2 To FirstLastRow - 2 '-2 is needed because of ReDim arr(SecondLastRow - 2)
For j = 2 To SecondLastRow - 2
If Search(i) = arr(j) Then
Worksheets(1).Cells(j, 3) = Worksheets(2).Cells(i, 3)
End If
Next j
Next i
End Sub

Related

VBA Concatenate 2 columns without loop

I have my column B with Strings like "6L2AAB".
My column D with Strings like "E3"
I'd like to produce in my column J the concatenation of B&D, for instance "6L2AABE3", for each row
My code throws a "13" error
.Range("J:J").Value = .Range("B:B").Value & "" & .Range("D:D").Value
Is there a way to do this without a loop ? Thanks
Edit: added loop-based approach to compare timing. Loop is faster!
Your code doesn't work because (eg) .Range("B:B").Value returns a 2-dimensional array, and you can't concatenate the contents of two arrays using &
You can use the worksheet's Evaluate method:
Sub tester()
Dim t, i As Long, arr1, arr2, arr3, x As Long
t = Timer
With ActiveSheet
.Range("J:J").Value = .Evaluate("=B:B&D:D")
End With
Debug.Print "Evaluate", Timer - t
t = Timer
With ActiveSheet
arr1 = .Range("B:B").Value 'read input values
arr2 = .Range("D:D").Value
ReDim arr3(1 To UBound(arr1), 1 To 1) 'size array for output
For i = 1 To UBound(arr1, 1) 'loop and concatenate
arr3(i, 1) = arr1(i, 1) & arr2(i, 1)
Next i
.Range("J:J").Value = arr3 'populate output to sheet
End With
Debug.Print "Loop", Timer - t
End Sub
Maybe don't run it on the whole column unless you really need that though.
#Tim Williams It really depends on the number of rows, if you are working with larger amounts of data loops start to slow slightly and evaluate will jump ahead in processing speed just slightly. But there is another option that is faster than both, the .formula method. See Tim's code with it added in below using > 1 million rows with .formula added.
Average for me:
Formula 1.664063
Evaluate 3.675781
Loop 3.824219
Sub tester()
Dim t, i As Long, arr1, arr2, arr3, x As Long
t = Timer
With ActiveSheet
.Range("C:C").Formula = "=A:A&B:B"
End With
Debug.Print "Formula", Timer - t
t = Timer
With ActiveSheet
.Range("C:C").Value = .Evaluate("=A:A&B:B")
End With
Debug.Print "Evaluate", Timer - t
t = Timer
With ActiveSheet
arr1 = .Range("A:A").Value 'read input values
arr2 = .Range("B:B").Value
ReDim arr3(1 To UBound(arr1), 1 To 1) 'size array for output
For i = 1 To UBound(arr1, 1) 'loop and concatenate
arr3(i, 1) = arr1(i, 1) & arr2(i, 1)
Next i
.Range("C:C").Value = arr3 'populate output to sheet
End With
Debug.Print "Loop", Timer - t
End Sub

Calling LinEst and INDEX Fuctions

I am trying to write a module that makes a simple linear regression many times over many different data ranges. For this i am using a loop where i use the LinEst function inside INDEX function so that i can extract the slope and the intercept.
The problem i am having is that at the line 9 the program crashes sending me an
error(5): "Invalid argument or procedure call".
Edited: Corrections Made.
Now at line 11 it says:
error(1004): "can not read the LinEst property of the worksheet function class".
Update: it's working fine now i just deleted the word WorksheetFunction
Sub regression()
Dim i As Integer
Dim j As Integer
Dim range1 As Range
Dim range2 As Range
j = 139
i = 4
For i = 4 To 54 Step 1
Set range2 = Range(Sheets(1).Cells(22, j), Sheets(1).Cells(66, j))
Set range1 = Range(Sheets(1).Cells(22, j - 1), Sheets(1).Cells(66, j - 1))
Sheets(12).Cells(i, 2) = Application.WorksheetFunction.Index(Application.WorksheetFunction.LinEst(range1, range2), 1)
Sheets(12).Cells(i, 3) = Application.WorksheetFunction.Index(Application.WorksheetFunction.LinEst(range1, range2), 2)
j = j - 1
Next i
End Sub
You have the wrong syntax for the Range property.
If you want to refer to a range from Cells(22, j) to Cells(66, j), and both those cells are on Sheets(1), then instead of :
range2 = Sheets(1).Range(Cells(22, j), Cells(66, j))
you'd use:
range2 = Range(Sheets(1).Cells(22, j), Sheets(1).Cells(66, j))
or:
With Sheets(1)
range2 = Range(.Cells(22, j),.Cells(66, j))
End With
In this case the first loop would assign range2 an object representing $EI$22:$EI$66.
The following line has the same issue.

Array already dimentioned/subscript out of range

If I set an array as dynamic, I get a subscript out of range error. If I set it as static, I get an array already dimentioned error. What am I missing?
Dim arrTrips() As String 'dynamic
Dim arrTrips(1 To 99) As String 'static
...
For i = 2 To lastrow
If .Cells(i, "C").Value2 = Target.Value2 Then
ReDim Preserve arrTrips(UBound(arrTrips) + 1) <-- error here
arrTrips(UBound(arrTrips)) = .Cells(i, "M").Value2
Debug.Print arrTrips(UBound(arrTrips))
End If
Next
Edit: adding more context. I am adding items to the array inside a loop.
LOL I fixed it. It's so stupid. All I did was add ReDim arrTrips(1) As String right below Dim arrTrips() As String
As mentioned, you're intializing an empty array, the Ubound function fails on that. There is no built-in method to test whether an array is "empty" so you need to either use error-trapping or a UDF that encapsulates error-trapping in order to determine whether the array has no dimensions.
Alternatively, since you know at the onset that the array is empty, you can simply ReDim it to something, and then you can later ReDim Preserve it within your loop.
Sub staticArra()
Dim arrTrips() As String
ReDim arrTrips(1 To 1) As String
Dim i As Integer
For i = 1 To 10
ReDim Preserve arrTrips(LBound(arrTrips) To UBound(arrTrips) + 1) As String
Next
End Sub
The second one, using ReDim arrTrips(1 to 99) fails to compile, by design.
https://learn.microsoft.com/en-us/office/vba/Language/Reference/user-interface-help/redim-statement
The ReDim statement is used to size or resize a dynamic array that has already been formally declared by using a Private, Public, or Dim statement with empty parentheses (without dimension subscripts).
But the ideal approach (in terms of conserving lines & avoiding redundant calls) would be to instantiate your array using ReDim instead of Dim, with a subscript. You can ReDim Preserve this:
Sub f()
ReDim arrTrips(0) As String
Dim i As Integer
For i = 1 To 10
If (i - 1) > UBound(arrTrips) Then
ReDim Preserve arrTrips(i)
End If
Next
End Sub
In short: give your array an upper bound to begin with.
Declaration 1
Dim arrTrips() As String
This is dynamic but its empty. When you do a Ubound on this, compiler will not find a size and will throw an error. Below will work since it is not derived from your arrTrips array
ReDim Preserve arrTrips(10)
Declaration 2:
Dim arrTrips(1 To 99) As String
This being static array, it can only be dimensioned once and will throw an error if you try to redimesion it.
You can use a counter:
Dim arrTrips() As String
...
Dim k As Long
k = 1
For i = 2 To lastrow
If .Cells(i, "C").Value2 = Target.Value2 Then
ReDim Preserve arrTrips(1 to k)
arrTrips(k) = .Cells(i, "M").Value2
Debug.Print arrTrips(k)
k = k + 1
End If
Next
You can also use COUNTIF to set the size of the array before the loop:
Dim arrTrips() As String
...
Dim k As Long
k = Application.CountIf(.Range("C:C"), Target.Value2)
ReDim arrTrips(1 To k)
Dim j As Long
j = 1
For i = 2 To lastrow
If .Cells(i, "C").Value2 = Target.Value2 Then
arrTrips(j) = .Cells(i, "M").Value2
Debug.Print arrTrips(j)
j = j + 1
End If
Next

Array() = range().value

I saw array() = range().value in an example and I'm trying to understand how it works.
Sub test()
Dim arr() As Variant
arr() = Range("E5:E7").Value
For i = 0 To UBound(arr)
Debug.Print arr(i)
Next i
End Sub
First, above code is giving me subscript out of range error. How come ? Second, what part of the documentation would let me know how array() = range().value would play out without testing it ? My hypothesis is that it will go through the cells from the top left to the bottom right and add them to the array. How can I confirm that though ?
I see two issues with your code. The first is that you start i at 0, but arrays in Excel begin at index 1. Instead of For i = 0 you can use For i = LBound(arr) like you use UBound(arr) or just start it at 1.
Second, and more importantly, an array of cells has both columns and rows. When you read a range into a variant array, you get a two-dimensional result (rows and columns) and not a one-dimensional result as you seem to be expecting.
Try this:
Sub test()
Dim arr() As Variant
Dim i As Long, j As Long
arr() = Range("E5:E7").Value
For i = 1 To UBound(arr, 1)
For j = 1 To UBound(arr, 2)
Debug.Print arr(i, j)
Next j
Next i
End Sub
If you want to get just the values of the cells into a one dimensional array, you can do that by using the Transpose function, like this:
arr() = Application.WorksheetFunction.Transpose(Range("E5:E7").Value)
If you do this, the array is now one-dimensional and you can iterate through it like you were trying to.
arr() = Application.WorksheetFunction.Transpose(Range("E5:E7").Value)
For i = 1 To UBound(arr)
Debug.Print arr(i)
Next i
This is a good read for you: http://www.cpearson.com/excel/ArraysAndRanges.aspx
The reason you're getting "out of range" is because it returns a 2 dimensional array.
Your line of code For i = 0 To UBound(arr) should be For i = 1 To UBound(arr,1)
Also, the array starts at 1, so don't use the 0 For i = 1 to UBound(arr, 1)
Your corrected code would be:
Sub Test()
Dim arr() as Variant
arr = Range("E5:E7")
For i = 1 To UBound(arr, 1)
MsgBox (arr(i, 1))
Next i
End Sub
It's basically loading the cell values of E5 - E7 into an array. But it is going to be two dimensional. So you will need Debug.Print arr(i, 1)
Sub test()
Dim arr() As Variant
arr() = Range("E5:E7").Value
For i = 1 To UBound(arr)
Debug.Print arr(i, 1)
Next i
End Sub

Copy single row range to array then pass ByRef to function VBA

I've been struggling with this code here (probably very simple mistake), would anyone mind pointing out where my issues are? My overall goal is to allow this subroutine to accept a range of variable size, however I can't seem to get it to work for a fixed size.
If I manually allocate the array, things work as expected but when I allocate with a range that's where things go wrong. The output comes back untouched, which leads me to believe that I'm not doing something correctly with the allocation. Also I'm getting errors when I try to pass ws.UsedRange as oppose to a fixed range.
Private Sub InsertionSort(ByRef a(), ByVal lo0 As Long, ByVal hi0 As Long)
Dim i As Long, j As Long, v As Long
For i = lo0 + 1 To hi0
v = a(i)
j = i
Do While j > lo0
If Not a(j - 1) > v Then Exit Do
a(j) = a(j - 1)
j = j - 1
Loop
a(j) = v
Next i
End Sub
Sub runSort()
Dim ws As Worksheet
Set ws = ActiveWorkbook.ActiveSheet
Dim myArr() As Variant
Dim rangeUse As Range
With ws.Range("D17:K17")
ReDim myArr(1 To 1, 1 To ws.Range("D17:K17").Columns.Count)
myArr = ws.Range("D17:K17").Value
End With
Call InsertionSort(myArr, LBound(myArr), UBound(myArr))
Range("D19:K19") = myArr
End Sub
Any help would be appreciated! TIA
So considerating you only want to sort your 2-dimensional array row by row, this might be a useful starting point. You can always change With ws.Range("A2:A3") to With Selection. If you do so, you have the Range you selected with your cursor.
With ws.Range("A2:A3")
myArr = .Value
For i = 1 To .Rows.Count
ReDim tmpArr(1 To .Columns.Count)
For j = 1 To .Columns.Count
tmpArr(j) = myArr(i, j)
Next j
Call InsertionSort(tmpArr, 1, .Columns.Count)
For j = 1 To .Columns.Count
myArr(i, j) = tmpArr(j)
Next j
Next i
.Offset(RowOffset:=10) = myArr
End With
Detailed Description
You don't have to redim myArray because if you set it to a range, it automatically scales.
tmpArr is each row of your range. If you select your range with the cursor some rows might be shorter or longer than others, thats why we redim that one. Edit This doesn't work just yet, because .Columns.Count refers to the whole range, not just the row. If you have different column counts then you'd have to change that.
For j = 1 To .Columns.Count
tmpArr(j) = myArr(i, j)
Next j
Unfortunately we cannot use tmpArr = myArr(i) because only one dimension of a multidimensional array cannot be accessed like this in VBA.
Call InsertionSort(tmpArr, 1, .Columns.Count) calles your Insertion Sort algorithm and sorts one row at a time.
After tmpArray got sorted, we have to set myArray(i) to the new values with the same loop we already used:
For j = 1 To .Columns.Count
myArr(i, j) = tmpArr(j)
Next j
Now we sorted all the rows in our Range, now we can put it back on the sheet, 10 rows beneath the first row of the specified range with .Offset(RowOffset:=10) = myArr
I hope that this helps you! While testing I saw that you might have a little bug in your InsertionSort algorithm. If the first value is the smalles, it just blindly gets copied into all the other fields of the array :)

Resources