My code need more than one hours to complete for 3500 rows but I need to work for more than 40000 rows data.
I am looking for alternatives to my code by using dictionary, with improved performance within the context of interest.
Could anyone help me?
Sub StripRow2Node()
'Read the Strip Design table
With Sheets("Design-Moment")
Sheets("Design-Moment").Activate
LastR1 = .Range("B" & Cells.Rows.Count).End(xlUp).Row
DM_arr = .Range(Cells(1, 1), Cells(LastR1, 7)) 'Col 1 to Col 7
DM_count = UBound(DM_arr, 1)
End With
'Read the x and y coordinations and thickness of a node in node design
With Sheets("Design-Shear")
Sheets("Design-Shear").Activate
LastR2 = .Range("B" & Cells.Rows.Count).End(xlUp).Row
DS_arr = .Range(Cells(1, 4), Cells(LastR2, 5)) 'Col 4 to Col 5
SX_arr = .Range(Cells(1, 26), Cells(LastR2, 27))
SY_arr = .Range(Cells(1, 30), Cells(LastR2, 31))
DS_count = UBound(DS_arr, 1)
End With
'** Find correponding reference row in Design-Moment for nodes**
'Match node to striip station and output row index
For i = 5 To DS_count
XStrip = SX_arr(i, 1)
XStation = DS_arr(i, 1)
YStrip = SY_arr(i, 1)
YStation = DS_arr(i, 2)
For j = 5 To DM_count
If DM_arr(j, 1) = XStrip Then 'X-Strip Name is matched
If DM_arr(j, 4) >= XStation And DM_arr(j - 1, 4) < XStation Then
SX_arr(i, 2) = j 'matched row reference for X-strip
End If
End If
If DM_arr(j, 1) = YStrip Then
If DM_arr(j, 5) <= YStation And DM_arr(j - 1, 5) > YStation Then
SY_arr(i, 2) = j
End If
End If
Next j
Next i
'Write the matched strip information to node
For i = 5 To LastR2
With Sheets("Design-Shear")
.Cells(i, 27) = SX_arr(i, 2)
.Cells(i, 31) = SY_arr(i, 2)
End With
Next i
End Sub
I suspect that almost all the time is being used writing back cell-by-cell to the sheet here:
'Write the matched strip information to node
For i = 5 To LastR2
With Sheets("Design-Shear")
.Cells(i, 27) = SX_arr(i, 2)
.Cells(i, 31) = SY_arr(i, 2)
End With
Next i
Writing back to Excel is much slower than reading from Excel.
I would suggest switching off screen updating and calculation, accumulating the results (currently X_arr(i, 2) and SY_arr(i, 2)) in separate arrays and then writing the arrays back to a range in a single operation rather than cell-by-cell
There are several points to improve:
1. Use qualified references to avoid.activate statements
You start off nicely with
With Sheets("Design-Shear")
...
DS_arr = .Range(Cells(1, 4), Cells(LastR2, 5)) 'Col 4 to Col 5
but fail to make the Cells objects refer to the With block. Instead use
With Sheets("Design-Shear")
...
DS_arr = .Range(.Cells(1, 4), .Cells(LastR2, 5)) 'Col 4 to Col 5
Now you do not have to activate the sheet anymore.
From the code I have to assume that there is only one possible match returned in this statement:
SX_arr(i, 2) = j
for all i; otherwise, the second, third...occurrence would overwrite this value of j. If that is indeed the case you can stop looping over j once a match is found:
SX_arr(i, 2) = j 'matched row reference for X-strip
Exit For
Shortcut both If statements if DM_arr(j, 1) can match XStrip or YStrip. If these matches are mutually exclusive, use ElseIf instead of If for the second statement.
Shortcutting the j-loop should improve the runtime noticeably. Of course, if you need the last matching index (instead of the first) then this will not apply.
edit:
For a dictionary solution, see for instance the excellent code from Jeeped here: https://codereview.stackexchange.com/questions/133664/searching-values-of-range-x-in-range-y
Related
In my quest to improve the quality of life at work, I've searched for an answer and wound up borrowing this code (posted my current attempt at bottom of the post) to extract differences between two worksheets. While it returns the basic information, it is less QoL change than my current method, which, while it works most of the time, still fails. The current method is as follows:
=IF(COUNTIFS(New!$H:$H, Old!$H2, New!$C:$C, Old!$C2,New!$B:$B, Old!$B2)<1, Old!$H2, "")
This code spans across several columns to populate the appropriate information (appointment time, date, patient name, patient ID, notes, etc). This goes on a sheet called "Removed", and I have one for "Added" where New and Old are reversed.
I attempted to modify the borrowed code to paste entire rows instead of just one column, but I seem to be failing at every turn, mainly because I am new to VBA and do not have a full grasp of it yet. Changing the first For loop to:
For i = LBound(valsM, 1) To UBound(valsM, 1)
If IsError(Application.Match(valsM(i, 1), valsQ, 0)) Then
mm = mm + 1
Worksheets("Old").Cells(i).EntireRow.Copy Destination:= Worksheets("New").Cells(mm, 1)
End If
Next i
is obviously the incorrect way, and I suspect it's due to the whole thing being based on arrays. What must I change in the script to accommodate 16 columns of information that must be moved over to appropriate pages? Bonus would be putting them all on one page and appending a 17th column Q that indicates removed or added. Appreciate the help.
Sub YouSuckAtVBA()
Dim i As Long, mm As Long
Dim valsM As Variant, valsQ As Variant, valsMM As Variant
With Worksheets("New")
valsM = .Range(.Cells(1, "A"), .Cells(.Rows.Count, "A").End(xlUp)).Value2
End With
With Worksheets("Old")
valsQ = .Range(.Cells(1, "H"), .Cells(.Rows.Count, "H").End(xlUp)).Value2
End With
ReDim valsMM(1 To (UBound(valsM, 1) + UBound(valsQ, 1)), 1 To 2)
mm = 1
valsMM(mm, 1) = "value"
valsMM(mm, 2) = "missing from"
For i = LBound(valsM, 1) To UBound(valsM, 1)
If IsError(Application.Match(valsM(i, 1), valsQ, 0)) Then
mm = mm + 1
Worksheets("Old").Cells(i).EntireRow.Copy Destination:=Worksheets("New").Cells(mm, 1)
End If
Next i
For i = LBound(valsQ, 1) To UBound(valsQ, 1)
If IsError(Application.Match(valsQ(i, 1), valsM, 0)) Then
mm = mm + 1
Worksheets("New").Cells(i).EntireRow.Copy Destination:=Worksheets("Old").Cells(mm, 1)
End If
Next i
valsMM = helperResizeArray(valsMM, mm)
With Worksheets("Test")
With .Cells(.Rows.Count, "A").End(xlUp).Offset(1, 0)
.Resize(UBound(valsMM, 1), UBound(valsMM, 2)) = valsMM
End With
End With
End Sub
Function helperResizeArray(vals As Variant, x As Long)
Dim arr As Variant, i As Long
ReDim arr(1 To x, 1 To 2)
For i = LBound(arr, 1) To UBound(arr, 1)
arr(i, 1) = vals(i, 1)
arr(i, 2) = vals(i, 2)
Next i
helperResizeArray = arr
End Function
If you have Office 365 you can use the new Filter-Function
The screenshot shows the formulas using a very basic example.
"Table old" and "Table new" are created via "Insert > Table" therefore it is possible to reference the column names within the formula instead of B or D
Firstly, I would like to thank you in advance for all the answers.
I've got an "end" array which contains row numbers.
If OptionButton1 = True Then
Dim arraydodrukowania() As String
ReDim arraydodrukowania(1 To o, 1 To 2)
For j = LBound(array1) To UBound(array1)
If array1(j, 9) = 1 And array1(j, 8) = ComboBox2.Value Then
arraydodrukowania(j, 1) = array1(j, 9)
arraydodrukowania(j, 2) = array1(j, 2)
Else
End If
what I want to do is to select all rows defined by array values
I know that the selection method should be avoided, but it can not be omitted at this occasion.
So, in other words, I need a code which will loop trough the array and will add row no defined by the value in the array to selection.
The result would be (for example) row no. 2, 5, 1456, 2354 are selected.
the problem here is that the number of rows that needs to be selected varies.
So
For j = LBound(array1) To UBound(array1)
array1(j,1).<ADD TO SELECTION>
next j
Any ideas?
Copy and Pasting an entire row based off of two conditions
For a school project, I am trying to find all the rows that satisfy both (of two) conditions then paste the entire row at the end of my data set. I am trying to do this with variables as the spreadsheet may change in tests that my professor will perform. I keep getting a "Subscript out of range" error. My reading and assigning to P and T, for loop, if statements, and count functions all work.
numrow = Rows(Rows.Count).End(xlUp).row
numcolumn = Columns(Columns.Count).End(xlUp).Column
P = Range(Cells(3, 1), Cells(numrow, 1)).Value
T = Range(Cells(3, 2), Cells(numrow, 2)).Value
For i = LBound(P, 1) To UBound(P, 1)
If P(i, 1) = 5 And T(i, 1) = 100 Then
countrow = countrow + 1 'check: return is 25
'Range(i, numcolumn).copy Sheets("Sheet1").End(xlUp).Offset(1, 0)
Worksheets("Sheet1").Range(Cells(i, numcolumn)).Value.copy
lastrow = ActiveSheet.Cells(1, 1).CurrentRegion.Rows.Count + 1
Range(Cells(lastrow, 1)).PasteSpecial xlPasteFormulasAndNumberFormats
Application.CutCopyMode = False
'I would also like to change all the cells that were just pasted in the first column to the value of 2.5 here, but I have no idea where to start with that
End If
Next i
As you can see I tried it two ways
1.)
Range(i, numcolumn).copy Sheets("Sheet1").End(xlUp).Offset(1, 0)
(which is commented for now)
2.)
Worksheets("Sheet1").Range(Cells(i, numcolumn)).Value.copy
both get highlighted when I try to debug and have the "subscript out of range" error
I'm a beginner at using visual basic, and I've run into a little problem. What I want to do is take an excel spreadsheet and search through a specific column in order to find a name, then grab everything in that row in order to transfer it over to another sheet. I've got everything running except that I run into an endless loop for a reason I do not understand.
'set up a for loop that increments through all sheets in the workbook
For i = 1 To ThisWorkbook.Sheets.Count
'set up a temp page to work with the current page
Set tem = ThisWorkbook.Sheets(i)
'increment through all the rows that have data in them
For Each rng In tem.Rows
'if the data matches what was searched for, copy it into another worksheet
If tem.Cells(ct, 4) = SForm.Text Then
sr.Cells(spot, 1) = tem.Cells(ct, 1)
sr.Cells(spot, 2) = tem.Cells(ct, 2)
sr.Cells(spot, 3) = tem.Cells(ct, 3)
sr.Cells(spot, 4) = tem.Cells(ct, 4)
sr.Cells(spot, 5) = tem.Cells(ct, 5)
sr.Cells(spot, 6) = tem.Cells(ct, 6)
sr.Cells(spot, 7) = tem.Cells(ct, 7)
sr.Cells(spot, 8) = tem.Cells(ct, 8)
sr.Cells(spot, 9) = tem.Cells(ct, 9)
sr.Cells(spot, 10) = tem.Cells(ct, 10)
sr.Cells(spot, 11) = tem.Cells(ct, 11)
sr.Cells(spot, 12) = tem.Cells(ct, 12)
'increment the placeholder for the new sheet
spot = spot + 1
End If
'increase ct to keep track of where in the worksheet it is
ct = ct + 1
Next rng
'reset ct for the next worksheet
ct = 1
Next i
The Specific issue I run into, is an overflow because ct is an int and maxes out. which tells me I have an endless loop on my hands.
Thanks for the help in advance.
You are iterating over 1 million times per sheet, this is going to take a while. Find the last cell in column D and only iterate through those rows
Plus you do not need ct. instead use rng.row in its place.
Then combine the whole value assignation into one line.
For i = 1 To ThisWorkbook.Sheets.Count
'set up a temp page to work with the current page
Set tem = ThisWorkbook.Sheets(i)
'increment through all the rows that have data in them
For Each rng In tem.Range("D1", tem.Cells(tem.Rows.Count, 4).End(xlUp))
'if the data matches what was searched for, copy it into another worksheet
If rng.Value = SForm.Text Then
sr.Range(sr.Cells(spot, 1), sr.Cells(spot, 12)).Value = tem.Range(tem.Cells(rng.Row, 1), tem.Cells(rng.Row, 12)).Value
'increment the placeholder for the new sheet
spot = spot + 1
End If
Next rng
Next i
To make it even quicker look into dropping the whole range into an array and output into another array to minimize the interaction between the sheets and vba.
The code I have once worked to add/delete groups of rows (requirements). I needed to modify the code so that if the 1st row of the group met certain criteria (i.e, the requirement was not one we wanted to consider), (1) we would not count it and (2), we would hide the group (current and subsequent 2 rows). This all works fine.
The problem is that now that I incorporated these changes, I get an error in another section of the code and for the life of me I cannot figure out why. I have stepped through this and am extremely frustrated. I am reaching out for help, and am hoping someone can see my error(!)
We calculate the start and finish row numbers within a grouping, and store these calculations in Arrays called "Start" and "Finish." I use the ReDim statement to initialize my arrays, because I thought that could be part of the problem, but no.
Any insight as to why my "subscripts are out of range" would be appreciated. I have traced through the logic, investigated this error, and read about the syntax/usage of VBA arrays. I don't know what else to do. Thanks in advance. Here are the relevant lines:
Sub Button1_Click()
Cells.Select
Selection.ClearOutline
If Cells.EntireRow.Hidden Then Cells.EntireRow.Hidden = False
Dim Start() As Integer
Dim Finish() As Integer
Dim p As Integer, q As Integer
ReDim Start(0, 50)
ReDim Finish(0, 50)
The following is embedded in logic that loops through all the rows in the spreadsheet:
i = 1
For Row = 4 To Cells(1, 6).Value - 1
If Begin Then
If Cells(Row, 3).Interior.ColorIndex = 44 Then
Start(i) = Row + 1
j = Cells(Row, 2).Value
Begin = False
End If
Else
If Cells(Row, 2).Value = j + 1 Or Cells(Row, 2).Interior.ColorIndex = 37 Then
Finish(i) = Row - 1
Begin = True
i = i + 1
Row = Row - 1
End If
End If
Next
The block I changed is as follows (code I added is last block where I attempt to hide rows). It precedes the previous. I am wondering how my change could have affect the above(?!)
If Cells(Row, 5).Value = "Requirement" Then
Range(Cells(Row, 4), Cells(Row, 4)).Interior.ColorIndex = 40
Rows(Row).Font.Bold = True
Rows(Row).Font.Italic = False
Rows(Row).Font.ColorIndex = 1 'Black
If Cells(Row - 3, 4).Value = "" Then 'this is requirement #1
Cells(Row, 4).Value = 1
Else
Cells(Row, 4).Value = Cells(Row - 3, 4).Value + 1
End If
p = Row
q = p + 2
Rows(p & ":" & q).Select
If Cells(p, 19).Value = "4" Then
Selection.EntireRow.Hidden = True
Else
Selection.EntireRow.Hidden = False
End If
Redim Start(0,50) makes the array dimensions 0 to 0, 0 to 50 i.e. a 2d array.
This means that when you call the array you need to provide parameters for both dimensions I.E: Start(0,i) or Finish(0,i). Calling Start(i) will result in the error you mentioned.
The easiest way to get rid of the error is to change your Redim lines to
ReDim Start(50)
ReDim Finish(50)
which is what I assume you meant to do in the first place.
Note: Based upon the format you used, you may have been meaning to do Start(0 to 50) and Finish(0 to 50) initially. The comma in the dimensioning indicates another dimension instead of a separation between lower and upper bounds.