Put range into an array for more efficient loops - vba

I have a bunch of loops in a function and they loop over an array that I defined as a dynamic range. Below is some pseudocode of what I want to achieve
Dim myArray As Range
myArray(x, j) 'So the array loops through each column and then each row in that column
For i = 1 to lastRow
If myArray(x, j).Value = myArray(x, i).Value Then
'Do something
I have a bunch of these loops and it's super slow with datasets of 100+ rows. Basically everywhere where I have myArray defined as a Range, I want to change it to a Variant so I can a) loop over the array and b) use the array to check if values are the same instead of checking the range against a range, which is probably the root cause of the performance issues when there are 200 rows * 500 columns
Edit
How can I convert a dynamically defined range into an array?
Do I need to do something like this?
lastRow = UBound(myArray, 1)
lastColumn = UBound(myArray, 2)
And then
If myArray(x, j) = myArray(x, i) Then
'Do something

To load a range into an array:
Dim RngArr() as Variant
RngArr = WorkSheets("Sheet1").Range("A1:D4").Value
This will create and array that is 4 x 4.
To make the range dynamic
Dim RngArr() as Variant
Dim lastrow as Long
Dim lastColumn as Long
lastrow = 10
lastColumn = 10
With WorkSheets("Sheet1")
RngArr = .Range(.Cells(1,1),.Cells(lastrow,lastColumn)).Value
End With
When loading the array this way the lower bound of both dimensions is 1 and not 0 as it would be otherwise.
To iterate through the array:
Dim i as long, j as long
For i = lbound(RngArr, 1) to Ubound(RngArr, 1)
For j = lbound(RngArr, 2) to Ubound(RngArr, 2)
'Do something with RngArr(i,j)
Next j
Next i
The second criteria of the lbound and ubound is the dimension.

Related

Dynamic Array for Sort and Clear contents

I have made an array for M2:Tx - The x will vary dependent code prior to this.
What I would like to do is have a 'clear contents' function and 'sort' function for this array (m2:tx).
I am just struggling to make the array DYNAMIC. If anyone could help.
I believe it will be something along these lines:
k = sh.Range("A3").End(xlDown).Row 'This will give me the length.
Dim TaskDates As Range
Dim StartCell As Range 'First part of Array
Dim EndCell As Range 'End of Array
Set EndCell = Range("T" & 2 + k) 'maybe 2 or 3
Set StartCell = Range("M2")
Set TaskDates = Range(StartCell, EndCell) 'Dynamic Range
This will work i beleive, but is there a more succinct methodology.
I don't really understand the logic by which you are setting your range.
If you want the last row in the range that starts with M2 to be the same as the last occupied row in Column A, then. in general
LastRow = Cells(Rows.Count, "A").End(xlUp).Row
So to set your range M2:Tx where x = LastRow
Set TaskDates = Range("M2", "T" & Cells(Rows.Count, "A").End(xlUp).Row)
To sort the range, decide how you want it sorted, then record a macro.
Here is one way:
Sub MakeRange()
Dim x As Long, MyDynamicRange As Range
x = 123
Set MyDynamicRange = Range("M2:T" & x)
MsgBox MyDynamicRange.Address(0, 0)
End Sub

Is it more optimal to process Range values in for loop or with array

I want to know what is better to populate the cells in a Range:
Checking how many rows and columns I want to populate and use a For..Next loop to go through them accessing the cells (see my code below), or
Do the same but with a Variant array loaded with the cell data, or
Some other method?
Here is my code:
' Count number of rows and columns we have
rowCounter = CountRowsFunction
colCounter = CountColsFunction
' Do operations needed
For i = 2 To rowCounter
originalSheet.Cells(i, colCounter + 1).Value = Round(DateDiff("n", originalSheet.Cells(i, ColumnsIndex(2)).Value, originalSheet.Cells(i, ColumnsIndex(3)).Value) / 60, 2)
Next i
In this case, I'm accessing the cells directly. But, I believe this may cause unnecessary calls to the spreadhseet. Thanks
The usage of an array to set multiple cell values is vastly superior. In the example below the range A1:MZ390 (152100 cells) are set with two methods:
Set all cells to 1; iterate cells and set each cell value to 2
Set all cells to 1; assign range to array; iterate array and set all array values to 2; assign back to range
Method 2 takes less than a second and method 1 takes > 4 seconds on my PC.
In the example it iterates the array, but you can use less code lines and just do varData = 2 - but it is unlikely that people want to set a bunch of cell values to a constant.
Option Explicit
Sub Test()
Dim dt1 As Date, dt2 As Date
Dim lngX As Long, lngY As Long
Dim varData As Variant
Dim ws As Worksheet
Dim rng As Range
'set ws
Set ws = ThisWorkbook.Worksheets("Sheet1")
'for loop method - without screen updating
Application.ScreenUpdating = False
ws.Cells.Delete
Set rng = ws.Range("A1:MZ390")
dt1 = Now
rng.Value = 1
For lngY = 1 To rng.Rows.Count
For lngX = 1 To rng.Columns.Count
rng.Cells(lngY, lngX).Value = 2
Next lngX
Next lngY
dt2 = Now
Application.ScreenUpdating = True
Debug.Print "For loop (without screen updating) took: " & Format(dt2 - dt1, "s") & " seconds"
'array method
ws.Cells.Delete
Set rng = ws.Range("A1:MZ390")
dt1 = Now
rng.Value = 1
varData = rng.Value
For lngX = 1 To UBound(varData, 1)
For lngY = 1 To UBound(varData, 2)
varData(lngX, lngY) = 2
Next lngY
Next lngX
rng.Value = varData
dt2 = Now
Debug.Print "Array method took: " & Format(dt2 - dt1, "s") & " seconds"
End Sub
Arrays are more efficient I believe, I use them when using a lot of data.
You'd use something like this, add a break point at the start of the loop, and use the locals window to view the array a.
Dim a() As Variant
a = Range("a1:a10").Value
For i = 1 To UBound(a)
Next i

Define array to obtain data from range as "double" var type

I'm fairly new to VBA, so please bear with me.
I want to tell VBA to get an array from a range of cells. The user will paste a column of data into cell C2 so cells below C2 will be populated. The number of cells populated is up to the user.
I am also going to need each of the elements in the array to be taken as doubles as I'm going to make operations with them.
Therefore if the list is
1.2222
2.4444
3.5555
Then I need the array to preserve the decimal points.
How do I do this?
This is what I've got this fur, with no luck:
Set ThisWS = Excel.ActiveWorkbook.Worksheets("Hoja1")
Dim InputValues() As Double 'Define Array
Dim LRow As Long 'Define length of array
With Sheets("Hoja1")
LRow = .Range("C" & .Rows.count).End(xlUp).Row
End With
InputValues = ThisWS.Range("C2:C" & LRow).Value 'Error 13: data type doesn't match
End Sub
Thanks!
Excel.ActiveWorkbook. isn't needed in Excel, it is implied. I didn't need to type cast the cell value CDbl(.Cells(x, "C")).
Sub Example()
Dim InputValues() As Double
Dim lastRow As Long, x As Long
With Worksheets("Hoja1")
lastRow = .Range("C" & .Rows.Count).End(xlUp).Row
ReDim InputValues(lastRow - 2)
For x = 2 To .Range("C" & .Rows.Count).End(xlUp).Row
InputValues(x - 2) = CDbl(.Cells(x, "C"))
Next
End With
End Sub
This example is more efficient but won't make a noticeable difference unless you are working with a very large amount of data.
Sub Example2()
Dim InputValues() As Double, vInputValues As Variant
Dim x As Long
With Worksheets("Hoja1")
vInputValues = .Range("C2", .Range("C" & .Rows.Count).End(xlUp)).Value2
ReDim InputValues(UBound(vInputValues) - 1)
For x = 1 To UBound(vInputValues)
InputValues(x - 1) = CDbl(vInputValues(x, 1))
Next
End With
End Sub
Set ThisWS = Excel.ActiveWorkbook.Worksheets("Hoja1")
Dim CurRow As Long
Dim LRow As Long 'Define length of array
LRow = ThisWS.Range("C" & Rows.count).End(xlUp).Row
Dim InputValues(1 to LRow - 1) As Double 'Define Array
For CurRow = 2 to LRow
InputValues(CurRow - 1) = ThisWS.Range("C" & CurRow).Value
Next CurRow
End Sub
you can simply go like follows
Option Explicit
Sub main()
Dim InputValues As Variant 'Define Array
With Excel.ActiveWorkbook.Worksheets("Hoja1") ' refer to wanted worksheet
InputValues = .Range("C2", .Cells(.Rows.Count, 3).End(xlUp)).value 'fill array with values in column "C" cells from row 2 down to last non emtpy one
End With
End Sub
should you ever need to handle array values as of Double type, then you can use CDbl() function
In VBA you can assign .Value and .Value2 arrays only to a Variant
As a side note if the range is formated as table, you can just do something like
Dim InputValues() ' As Variant by default
InputValues = [transpose(Hoja1!Table1[Column3])] ' Variant(1 to number of rows in Table1)

Quickly filling up excel cells from a Array

I generate arrays from a external data tool and now want to fill the cells up using the data in the arrays. I have written the following code.
With ThisWorkbook.Worksheets("Data")
Lastrow = .Cells(.Rows.count, 2).End(xlUp).Row
For i = LBound(Price) To UBound(Price)
.Cells((Lastrow + 1), 1) = Price(i)
.Cells((Lastrow + 1), 2) = Quantity(i)
Lastrow = Lastrow + 1
Next i
End With
All the arrays are of same length and I have around 25 odd arrays to work with. The code works fine but the problem I am facing is of speed. It takes me around 5-6 hours to fill the sheet once with around 3000 as the length of array. Please suggest your best way. Thank you.
Here is an example of how to populate to a range from an array without looping:
Sub PopulateFromArray()
Dim MyArr As Variant
MyArr = Array("Hello", "World", "This is some", "Text")
Range("A1").Resize(UBound(MyArr) + 1, 1).Formula = Application.Transpose(MyArr)
End Sub
We are using resize to resize the range to populate using the upper boundary of the array. We add one to it because it is option base 0. We transpose the array because by the nature of an array the data goes across and we need it to go down. If we wanted to span columns instead of rows we would need to double transpose it like this:
Application.Transpose(Application.Transpose(MyArr))
With ThisWorkbook.Worksheets("Data")
NextRow = .Cells(.Rows.count, 2).End(xlUp).Row + 1
num = UBound(Price) - LBound(Price)
.Range(.Cells(NextRow, 1), .Cells(NextRow + num, 1)) = Application.Transpose(Price)
.Range(.Cells(NextRow, 2), .Cells(NextRow + num, 2)) = Application.Transpose(Quantity)
End With
you can dump an array to a worksheet range very simply like this:
range("A1:B5").value = myArray
you can populate an array conversly:
dim myArray as variant
myArray = range("A1:B5").value
I use this method very frequently, I hardly ever work with data on a worksheet, I prefer to take it into an array first then work with the array.
You have number of arrays (25) with different data (e.g. Price, Quantity, SomeOtherArray) as per your question. As per my comment above.
Option Explicit
Public Sub GetData()
Dim ws As Worksheet
Dim LastRow As Long
Dim arrPrice As Variant
Dim arrQty As Variant
Set ws = Sheets(3)
'-- starts at zero index
arrPrice = Array(50, 60, 70, 75)
arrQty = Array(250, 100, 50, 200)
'-- change columns as per your needs
LastRow = ws.Range("B" & ws.Rows.Count).End(xlUp).Row
'-- UBound + 1 is because the array starts at zero index above
ws.Range("B1").Offset(LastRow).Resize(UBound(arrPrice)+1).Value = Application.Transpose(arrPrice)
ws.Range("B1").Offset(LastRow, 1).Resize(UBound(arrQty)+1).Value = Application.Transpose(arrQty)
End Sub
To fill a range of N rows by M columns, put the data into a 2-dimensional array of the same size, then assign that array to the Value property of the range.
ReDim varValues(1 To lngRowCount, 1 To lngColCount)
Or
ReDim varValues(0 To lngRowCount - 1, 0 To lngColCount - 1)
I presume you can handle populating the array. Then:
Set rngTheRange = 'I presume you can handle this, too
rngTheRange.Value = varValues
Here is an example that uses this technique to fill the current selection with values 0 through N - 1, where N is the number of cells in the selection:
Option Explicit
Public Sub X()
Dim rngCurrent As Range
Dim lngRows As Long
Dim lngCols As Long
Dim lngR As Long
Dim lngC As Long
Dim varValues() As Variant
Set rngCurrent = Selection
lngRows = rngCurrent.Rows.Count
lngCols = rngCurrent.Columns.Count
ReDim varValues(0 To lngRows - 1, 0 To lngCols - 1) As Variant
For lngR = 0 To lngRows - 1
For lngC = 0 To lngCols - 1
varValues(lngR, lngC) = lngR * lngCols + lngC
Next
Next
rngCurrent.Value = varValues
End Sub

Extracting Data from Excel Sheet to VBA: Empty Variant/Array, but UBound Returns Number

I am trying to extract text data from an Excel sheet into an array (defined as a variant in this case).
The below code does not return desirable results: When I try to access an element in the SearchItems variant array, an error pops up saying subscript out of range.
However when I run UBound(SearchItems) the system returns the value of LR (instead of LR-1?).
In any case does that indicate that data if already loaded onto the array?
Sub Testing()
Dim SearchItems As Variant
Dim LR As Integer
LR = Sheets("MySheet").Cells(Rows.Count, "A").End(xlUp).Row 'Get number of cells in column A
SearchItems = Sheets("MySheet").Range("A1:A" & LR).Value
End Sub
You are dealing with a two dimensional array:
Sub Testing()
Dim SearchItems As Variant
Dim LR As Integer, i As Integer
LR = Sheets("MySheet").Cells(Rows.Count, "A").End(xlUp).Row 'Get number of cells in column A
SearchItems = Sheets("MySheet").Range("A1:A" & LR).Value
For i = 1 To LR
MsgBox SearchItems(i, 1)
Next i
End Sub
the array searchitems starts from 0, so of course ubound will add +1 to the size u think it has.
If you need Ubound to work (as the tittle of the post suggests) :
Sub Testing()
Dim SearchItems() As Variant 'we want SeachItems to be a dynamic array
Dim LR As Long, i As Long
with Sheets("MySheet")
LR = .Cells(.Rows.Count, 1).End(xlUp).Row 'an other way of Getting the number of cells in column A, note the '.' before rows
redim SearchItems ( 1 to LR, 1 to 1) ' this way ubound should work
SearchItems = .Range(.cells(1,1) , .cells(LR,1) ).Value 'an other way of doing it (strangely faster considering its bigger code, tested it)
end with
For i = 1 To LR 'or to Ubound (SearchItems)
'do stuff with SearchItems(i, 1)
Next i
'to write it back to the worksheet :
Sheets("MySheet").Range("A1:A" & LR).Value = SearchItems
End Sub