Faster way to execute loops - excel

I am new to VBA and need help in figuring out a faster way to execute my code. Here is the code I am using:
Sub loop()
For i = 1 To 100000
check_cell = Sheets("Sheet1").Range("I" & i)
For j = 1 To 14430
text_to_check = Sheets("Sheet2").Range("D" & j)
text_to_fill = Sheets("Sheet2").Range("E" & j)
If InStr(check_cell, text_to_check) Then
Sheets("Sheet1").Range("J" & i).Value = text_to_fill
End If
Next j
Next i
End Sub
I know that I am using a very brutal way by running the system through a loop 1,443,000,000 times. Any help on shortening this would be appreciated. Thanks.
EDIT: Based on suggestion, I tried with new code using variants but nothing seems to be happening. Would you be able to tell me what am I doing wrong here? Thanks
Sub loop_2()
Dim varray_1 As Variant
Dim varray_2 As Variant
Dim i As Long
Dim j As Long
varray_1 = Sheets("L1").Range("I2:I39997").Value
varray_2 = Sheets("Sheet2").Range("G1:G14394").Value
For i = UBound(varray_1, 1) To LBound(varray_1, 1) Step -1
For j = UBound(varray_2, 1) To LBound(varray_2, 1) Step -1
If varray_1(i, 1) = varray_2(j, 1) Then
Sheets("L1").Range("L" & i).Value = Sheets("Sheet2").Range("H" & j).Value
End If
Next j
Next i
End Sub

I haven't tested this code, but it should at least give an idea of how to put the values into arrays, process everything "in-memory", and then write the results out.
Sub loop()
Dim i As Long
Dim j As Long
Dim check_cell() As Variant
Dim result() As Variant
Dim text_to_check() As Variant
Dim text_to_fill() As Variant
check_cell = Sheets("Sheet1").Range("I1:I100000").Value
result = Sheets("Sheet1").Range("J1:J100000").Value
text_to_check = Sheets("Sheet2").Range("D1:D14430").Value
text_to_fill = Sheets("Sheet2").Range("E1:E14430").Value
For i = 1 To 100000
For j = 1 To 14430
If InStr(check_cell(i, 1), text_to_check(j, 1)) Then
result(i, 1) = text_to_fill(j, 1)
If i = 1 Then
Debug.Print "check_cell=" & check_cell(i, 1)
Debug.Print "j=" & j
Debug.Print "text_to_check=" & text_to_check(j, 1)
Debug.Print "text_to_fill=" & text_to_fill(j, 1)
End If
' exit as soon as first match is made
Exit For
End If
Next j
Next i
Sheets("Sheet1").Range("J1:J100000").Value = result
End Sub

The most cost here is using InStr(), but you should also:
Declare your variables, Variant are slower
Wrap your loops with
With Sheets("Sheet1")
...
End With
Change cell addressing to .Cell(10, i) instead of .Range("D" & j)
My tests show it runs 50% faster, note that I had all cells empty, so the InStr() cost is relatively low in that case.

Related

What options do I have to CreateObject("System.Collections.ArrayList")?

I have a file we share among a lot of people at work. The past five years the code has worked fine, but now a new guy is introduced to the file and he get the (links to other threads about the error) automation error on this part below:
Dim TillfLevdagar(1 To 14) As String
....
' code that sets some of the array items above
.....
Dim arr2 As Object
Set arr2 = CreateObject("System.Collections.ArrayList") ' here it errors
For Each itm In TillfLevdagar
If itm <> "" Then arr2.Add CInt(itm)
Next
arr2.Sort
For Each itm In arr2
msg = msg & itm & ","
Next
Since this is a work computer we are very limited in allowing any software to be installed. That includes Microsoft software.
Becuase of this I ask what alternatives do I have to the code above?
Please, try the next sorting way and use only the initial (TillfLevdagar) array:
Sub BubbleSort(arr)
Dim i As Long, j As Long, temp
For i = LBound(arr) To UBound(arr) - 1
For j = i + 1 To UBound(arr)
If arr(i) > arr(j) Then
temp = arr(i): arr(i) = arr(j)
arr(j) = temp
End If
Next j
Next i
End Sub
You can test it using the next testing procedure:
Sub testBubbleSort()
Dim arr, arrStr(1 To 14) As String, i As Long
arr = Split("test,boolean,core,leaf,dream,crises,bet,arrow", ",")
For i = 1 To 14
arrStr(i) = Chr(Int((25 + 1) * Rnd + 1) + 64) & "test"
Next i
BubbleSort arr
Debug.Print Join(arr, "|")
BubbleSort arrStr
Debug.Print Join(arrStr, "|")
End Sub
I have this sorting sub in my testing collection Subs, from some years. I do not remember where from I found it and what I adapted, if I did that. It is a simple sorting procedure. Anyhow, I used it many times with good results.

Move Entire Row To Another Sheet Starting at a different column Based On Cell Value

This is my following code that works, but I need it to paste it starting in Column C. When changing Range("A" & J + 2) to Range("C" & J + 2) it will not work then. I know its something easy but I have yet to find a specific example.
Thanks
Sub Patrick()
Dim xRg As Range
Dim xCell As Range
Dim i As Long
Dim J As Long
Dim K As Long
i = Worksheets("Patrick").UsedRange.Rows.Count
J = Worksheets("Sheet1").UsedRange.Rows.Count
If J = 1 Then
If Application.WorksheetFunction.CountA(Worksheets("Sheet1").UsedRange) = 0 Then J = 0
End If
Set xRg = Worksheets("Patrick").Range("A1:A" & i)
On Error Resume Next
Application.ScreenUpdating = False
For K = 1 To xRg.Count
If CStr(xRg(K).Value) = "Phone Call" Then
xRg(K).EntireRow.Copy Destination:=Worksheets("Sheet1").Range("A" & J + 2)
J = J + 1
End If
Next
Application.ScreenUpdating = True
End Sub
xRg(K).EntireRow.Copy copies a whole row - you can't paste that anywhere but the first column.
Use something more reasonable like
xRg(K).EntireRow.Columns("A:Z").Copy
or whatever will capture the data you have on your source sheet

How to create a nested loop to check if a value exists in a second list

I am trying to compare values in two lists. I want my code to compare a value in the first list and check all the entries in the second list. If there is a match then the code will print true next to the value in the first list and if not it will print false.
The problem I am having is that my code only compares values that are in the same row.
The code runs and I have tried it on a two smaller lists to make sure the data types are to same and there aren't any extra spaces or commas in the lists that would lead to a "False" output. I have also tried changing the order of the for and if statements but this doesn't work either.
Sub findvalues()
For i = 2 To 16
For j = 2 To 16
If Cells(i, 3).Value = Cells(i, 1).Value Then
Cells(i, 4).Value = "TRUE"
ElseIf Cells(i, 3).Value = Cells(j + 1, 1).Value Then
Cells(i, 4).Value = "TRUE"
Else
Cells(i, 4).Value = "FALSE"
End If
Next j
Next i
End Sub
Here are the two lists I am testing the code on
Slight mods to your code based on the data you provided in columns 1 & 3. As always, things could be improved but this should get you going ...
Sub findvalues()
Dim i As Long, j As Long, bResult As Boolean
For i = 2 To 16
strValueToLookFor = Cells(i, 1)
For j = 2 To 16
bResult = False
If strValueToLookFor = Cells(j, 3).Value Then
bResult = True
Exit For
End If
Next j
Cells(i, 6).Value = bResult
Next i
End Sub
... you may just need to flick the columns over so the first list searches on the second list or vice versa.
I don't see any need for VBA - formulas are the way to go - but to avoid two loops one could do this:
Sub findvalues()
Dim i As Long
For i = 2 To 130
Cells(i, 4).Value = IsNumeric(Application.Match(Cells(i, 1).Value, Range("C2:C130"), 0))
Next i
End Sub
Update: this does not cater for multiple matches.
There are many was to achieve that. one of them is by using IF & COUNTIF
Formula
=IF(COUNTIF($E$2:$E$6,A2)>0,"TRUE","FALSE")
Results:
VBA CODE
Option Explicit
Sub findvalues()
Dim i As Long
Dim rng As Range
With ThisWorkbook.Worksheets("Sheet1") 'Change if needed
Set rng = .Range("A2:A130") 'set rng to includes values from column A, rows 2:130
For i = 2 To 130 'Loop from row 2 to 130
'Check if the values in column C includes in the rng
If Application.WorksheetFunction.CountIf(rng, .Range("C" & i).Value) > 0 Then
.Range("D" & i).Value = "TRUE"
Else
.Range("D" & i).Value = "FALSE"
End If
Next i
End With
End Sub
VBA code to reconcile two lists.
Sub Reconciliation()
Dim endRow As Long
Dim ICount As Long
Dim Match1() As Variant
Dim Match2() As Variant
Dim ws As Worksheet
Set ws = Worksheets("Recon")
ICount = 0
endRow = ws.Cells(ws.Rows.Count, 2).End(xlUp).Row
endRow1 = ws.Cells(ws.Rows.Count, 11).End(xlUp).Row
Match1 = Sheet1.Range("b2:b" & endRow)
Match2 = Sheet1.Range("K2:K" & endRow1)
For i = LBound(Match1) To UBound(Match1)
For j = LBound(Match2) To UBound(Match2)
If Match1(i, 1) = Match2(j, 1) Then
ICount = ICount + 1
Sheet1.Range("C" & i + 1).Value = ICount
Sheet1.Range("L" & j + 1).Value = ICount
Else
End If
Next j
Next i
End Sub

Looping with condition. Do until numbers end

I have a cell with a full address. I want to copy street name and street number to the next cell. E.g "STRANDVEJEN 100 MIDDELFART DENMARK"
In this example I want "STRANDVEJEN 100" to be copied.
Currently everything is being copied to the next cell.
But I need advice how to continue
Sub move()
Range("C3:C2000").Copy Range("D3:D2000")
Do until......
End Sub
I need help with the do until part.
Try:
Option Explicit
Sub CopyYes()
Dim arr As Variant, strSplit As Variant
Dim i As Long
With ThisWorkbook.Worksheets("Sheet1")
arr = .Range("C3:C2000")
For i = LBound(arr) To UBound(arr)
strSplit = Split(arr(i, 1), " ")
.Range("E" & i + 2).Value = strSplit(0)
.Range("F" & i + 2).Value = strSplit(1)
Next i
End With
End Sub
Results:
Here is a function that will perform your 'cut' on a string. It's not pretty and it could no doubt be better written with a simple reg-ex command but..
Function untilnumeric(txt As String) As String
Dim i As Long
Dim started As Boolean
For i = 1 To Len(txt)
If Asc(Mid(txt, i, 1)) > 47 And Asc(Mid(txt, i, 1)) < 58 Then
started = True
Else
If started = True And Asc(Mid(txt, i, 1)) = 32 Then
untilnumeric = Left(txt, i - 1)
Exit For
End If
End If
Next
End Function
You could use it like this to perform it on column C - copying the result to D:
Range("D3:D2000").Value = Range("C3:C2000").Value
For Each c In Range("D3:D2000").Cells
c.Value = untilnumeric(c.Value)
Next
Note: Amended slightly to pick up any letters within the number part. eg.100A

Count string within string using VBA

I have product codes: (they are in C column of active sheet)
DO-001
DO-002
DO-003
DO-004
And I have big list of data: (they are in C column of "Sheet1")
41300100_DO-001_14215171
41300104_DO-001_14215173
K1_ISK_41300661_DO-002_13190369
NP_41533258_DO-003_14910884
DO-003_DD_44_ddd
And I want to count how many times do the product codes appear in the list of data. So the result for this case'd be: (result is H column of active sheet)
DO-001 2
DO-002 1
DO-003 2
DO-004
I have done this with this code:
Sub CountcodesPLC()
Dim i, j As Integer, icount As Integer
Dim ldata, lcodes As Long
icount = 0
lcodes = Cells(Rows.Count, 3).End(xlUp).Row
ldata = Worksheets("Sheet1").Cells(Rows.Count, 3).End(xlUp).Row
For i = 10 To lcodes
For j = 2 To ldata
If InStr(Worksheets("Sheet1").Range("C" & j), Range("C" & i)) <> 0 Then
icount = icount + 1
End If
Next j
If icount <> 0 Then
Range("H" & i).Value = icount
End If
icount = 0
Next i
End Sub
But I want to change it, so if the list of data contains some key words like "NP", "ISK", then not to count them, or if the first part of the data is the code then also not to count them, so the result for this example would be:
DO-001 2
DO-002
DO-003
DO-004
Also, I'll have around 1.000 product codes, and around 60.000 strings of data.
Will my code crash?
Seems your code is OK. But if you want to match only the first part of string (a'ka StartsWith), i'd change only this line:
If InStr(Worksheets("Sheet1").Range("C" & j), Range("C" & i)) <> 0 Then
to:
If Worksheets("Sheet1").Range("C" & j) Like Range("C" & i) & "*" Then
For further details, please see: Wildcard Characters used in String Comparisons
Use Dictionnary
Dim Dict As Scripting.Dictionary
Set Dict = New Scripting.Dictionary
Arr = Split("refer your text here", "_")
For I = LBound(Arr) To UBound(Arr)
If Dict.Exists(Arr(I)) Then
Dict(Arr(I)) = Dict(Arr(I)) + 1 'increment
Else
Dict.Add Arr(I), 1
End If
Next I
This may be OTT for the requirement but should work quite quickly.
Public Sub Sample()
Dim WkSht As Worksheet
Dim LngRow As Long
Dim AryLookup() As String
Dim VntItem As Variant
'We put what we want to search into an array, this makes it a lot quicker to search
Set WkSht = ThisWorkbook.Worksheets("Sheet1")
ReDim AryLookup(0)
LngRow = 1
Do Until WkSht.Range("A" & LngRow) = ""
If AryLookup(UBound(AryLookup, 1)) <> "" Then ReDim Preserve AryLookup(UBound(AryLookup, 1) + 1)
AryLookup(UBound(AryLookup, 1)) = Trim(UCase(WkSht.Range("A" & LngRow)))
LngRow = LngRow + 1
Loop
Set WkSht = Nothing
'Then we go down the list and check the array against each item
Set WkSht = ActiveSheet
LngRow = 1
Do Until WkSht.Range("A" & LngRow) = ""
WkSht.Range("B" & LngRow) = 0
For Each VntItem In AryLookup()
'This looks for the match without any of the exclusion items
If (InStr(1, VntItem, Trim(UCase(WkSht.Range("A" & LngRow)))) <> 0) And _
(InStr(1, VntItem, "NP") = 0) And _
(InStr(1, VntItem, "ISK") = 0) Then
WkSht.Range("B" & LngRow) = WkSht.Range("B" & LngRow) + 1
End If
Next
LngRow = LngRow + 1
Loop
Set WkSht = Nothing
MsgBox "Done"
End Sub
Basically, the 60,000 data strings will go into an array in memory, then the array will be searched against the 1,000 products. Searching in memory should be quick.
One thing I would raise is the exclusion method may produce false positives.
For example, excluding NP will exclude: -
NP_41533258_DO-003_14910884
NPA_41533258_DO-003_14910884
41533258_ANP_DO-003_14910884
You may want to think about the method overall.
Have you considered an array formula, not sure how it will perform vs code, but, you could do something along these lines, where list is in A and prod numbers in B
=SUM(IF(NOT(ISERR(SEARCH(B1 & "_",$A$1:$A$5,1)))*(ISERR(SEARCH("NP_",$A$1:$A$5,1))),1,0))
Where "NP" would be replaced by a range containing the exclusions, I've left as NP to show what's happening.
The code would be like this. But I don't know the speed.
Sub test()
Dim vDB, vLook, vSum(), Sum As Long
Dim Ws As Worksheet, dbWs As Worksheet
Dim s As String, sF As String, sCode As String
Dim i As Long, j As Long, n As Long
Set dbWs = Sheets("Sheet1")
Set Ws = ActiveSheet
With Ws
vLook = .Range("a1", .Range("a" & Rows.Count).End(xlUp))
End With
With dbWs
vDB = .Range("c1", .Range("c" & Rows.Count).End(xlUp))
End With
n = UBound(vLook, 1)
ReDim vSum(1 To n, 1 To 1)
For i = 1 To n
sF = Split(vLook(i, 1), "-")(0)
sCode = Replace(vLook(i, 1), sF, "")
Sum = 0
For j = 1 To UBound(vDB, 1)
s = vDB(j, 1)
If Left(s, Len(sF)) = sF Or InStr(s, "NP") Or InStr(s, "ISK") Then
Else
If InStr(s, sCode) Then
Sum = Sum + 1
End If
End If
Next j
If Sum > 0 Then
vSum(i, 1) = Sum
End If
Next i
Ws.Range("h1").Resize(n) = vSum
End Sub

Resources