1

I need to compare two ranges and see if value in one range appears in the other. This is the code I use:

Dim rng1 As Range
Dim rng2 As Range
Dim cell as Range
Dim found as Range

set rng1 = ....
set rng2 = ....
for each cell in rng1
set found = rng2.Find(what:=cell,.....
Next cell

This code is OK if the range is in thousands of rows, single column. When it comes to tens of thousands, it's very slow.

Anyway to speed it up?

Damian
  • 5,152
  • 1
  • 10
  • 21
joehua
  • 725
  • 3
  • 10
  • 25
  • 1
    Put one range into a dictionary, the other one into an array. loop through the array checking if every item exist on the dictionary. Barely seconds. – Damian Sep 27 '19 at 10:26
  • Try this: [Read/ write speeds](https://stackoverflow.com/questions/55379925/performance-difference-between-checking-a-cells-interior-colour-vs-its-value/55381841#55381841) – Dean Sep 27 '19 at 10:43

3 Answers3

3

This might be the fastest way for large amounts of data:

Option Explicit
Sub Test()

    Dim rng1 As Range
    Set rng1 = YourShorterRange

    Dim rng2 As Range
    Set rng2 = YourLargerRange

    Dim C As Range
    Dim Matches As Object: Set Matches = CreateObject("Scripting.Dictionary")
    'input the larger data inside a dictionary
    For Each C In rng2
        If Not Matches.Exists(C.Value) Then Matches.Add C.Value, 1
    Next C

    Dim i As Long
    Dim arr As Variant
    'input the shorter data inside an array
    arr = rng1.Value
    For i = 1 To UBound(arr)
        If Matches.Exists(arr(i, 1)) Then
            'your code if the value is found
        End If
    Next i

End Sub

Edit for Dorian:

Option Explicit
Sub Test()

    Dim rng1 As Range
    Set rng1 = YourShorterRange

    Dim rng2 As Range
    Set rng2 = YourLargerRange

    Dim i As Long, j As Long
    Dim arr As Variant
    Dim Matches As Object: Set Matches = CreateObject("Scripting.Dictionary")
    arr = rng1.Value
    'input the larger data inside a dictionary
    For i = 1 To UBound(arr)
        For j = 1 To UBound(arr, 2)
            If Not Matches.Exists(arr(i, j)) Then Matches.Add arr(i, j), 1
        Next j
    Next i

    'input the shorter data inside an array
    arr = rng2.Value
    For i = 1 To UBound(arr)
        For j = 1 To UBound(arr, 2)
            If Matches.Exists(arr(i, j)) Then
                'your code if the value is found
            End If
        Next j
    Next i

End Sub
Damian
  • 5,152
  • 1
  • 10
  • 21
  • On a timer, using `Dictionary` over `ArrayList` has been 4-5 seconds faster each time, running a sample data of 30000 rows. So definately the way to go. +1 – JvdV Sep 27 '19 at 10:57
  • definitely the better way to go. Using only arrays is also possible and will be just as fast. – Dean Sep 27 '19 at 10:57
  • @Dean how do you use only arrays? everytime you want to find data from one array to another, you need to loop the second so it's 2 loops. – Damian Sep 27 '19 at 10:58
  • @Damian yes, but because you are looping in memory this will still be "fast" even with large data set. And by "fast" relative to looping in range., but obviously not as quick as dictionary/ array method. – Dean Sep 27 '19 at 11:00
  • @Dean that's what a I meant, maybe I could improve by inserting the first range inside the array before looping to the dictionary... But array/dictionary still is the fastest way to go I think. – Damian Sep 27 '19 at 11:02
  • If you want to match more than one column of data you need to also loop through the columns @Dorian I was giving an example with 1D array data on both sides. – Damian Sep 27 '19 at 11:29
  • 1
    @Dorian there you go, with the Edit doesn't matter if it's 2-3 or 200 columns wide. – Damian Sep 27 '19 at 11:42
  • 1
    The first sub finished in 3 seconds, compared to my code which ran for 30 minutes and was only half done. Thanks. – joehua Sep 28 '19 at 09:47
  • When a match is found, i is the row number of the match in the short range. Anyway to find the row number of the match in the larger range? – joehua Sep 28 '19 at 10:39
  • @joehua instead `Matches.Add arr(i, j), 1` do `Matches.Add arr(i, j), i` and then you can know it's row by `Matches(arr(i, j))` – Damian Sep 28 '19 at 13:18
  • Thanks. That works. I had tried that on the top sub but it didn't work. – joehua Sep 29 '19 at 01:45
2

Maybe something along these lines:

Sub Test()

Dim arr1 As Variant, arr2 As Variant
Dim arrList As Object: Set arrList = CreateObject("System.Collections.ArrayList")
Dim x As Long

arr1 = rng1 'Specify your range
arr2 = rng2 'Specify your range

For x = LBound(arr2) To UBound(arr2)
    arrList.Add arr2(x, 1)
Next x

For x = LBound(arr1) To UBound(arr1)
    If arrList.contains(arr1(x, 1)) = True Then
        Debug.Print arr1(x, 1) & " contained within range 2"
    End If
Next x

End Sub
JvdV
  • 70,606
  • 8
  • 39
  • 70
  • JvdV is the arraylist case sensitive like dictionaries? – Damian Sep 27 '19 at 10:51
  • @Damian, yes it is case sensitive. – JvdV Sep 27 '19 at 10:57
  • @Damian, funny fact I just noticed testing speeds; checking if a value is contained within an arraylist before adding it (to slim down the size of the arraylist) actually decreased speed. Rather store the whole range :S – JvdV Sep 27 '19 at 11:20
  • That's interesting to know but I can't even work with arraylists... it always gives me error. – Damian Sep 27 '19 at 11:24
  • @Damian, no worries. Was just interested, cause maybe the same does apply to dictionaries (which contrary to you, I'm not so familiar with) =) – JvdV Sep 27 '19 at 11:26
  • JvdV I learned with this amazing [guide](http://www.snb-vba.eu/VBA_Dictionary_en.html) – Damian Sep 27 '19 at 11:28
0

I would suggest you :

Application.match

You can also look here you will find an interesting studies on 3 different ways of Search. Those 3 Different way will be studied By Time and By number of occurences.

According Fastexcel the conclusion of this study is :

Don’t use Range.Find unless you want to search a large number of columns for the same thing (you would have to do a Match for each column).

The Variant array approach is surprisingly effective, particularly when you expect a large number of hits.

Match wins easily for a small number of hits.

So If you except a large number of hit you might have to give a try variant array method. The 3 ways are listed in Fastexcel tuto

Edit

After reading some comment I did a new test :

enter image description here

Variant code

Sub Test1()
Dim vArr As Variant
Dim j As Long
Dim n As Long
Dim dTime As Double
dTime = MicroTimer
vArr = Range("A1:B100000").Value2

Dim Matches As Object: Set Matches = CreateObject("Scripting.Dictionary")
arr = Range("G1:G15").Value
    For i = 1 To UBound(arr)
        For j = 1 To UBound(arr, 2)
            If Not Matches.Exists(arr(i, j)) Then Matches.Add arr(i, j), 1
        Next j
    Next i

For j = LBound(vArr) To UBound(vArr)
If Matches.Exists(vArr(j, 1)) Or Matches.Exists(vArr(j, 2)) Then n = n + 1
Next j
Debug.Print "Using Variant : " & n & " Timer :" & (MicroTimer - dTime) * 1000
End Sub

Dictionary

Sub Test()


    Dim rng1 As Range
    Set rng1 = Range("A1:B100000")

    Dim rng2 As Range
    Set rng2 = Range("G1:G15")

    Dim i As Long, j As Long
    Dim arr As Variant

    Dim dTime As Double
    dTime = MicroTimer

    Dim Matches As Object: Set Matches = CreateObject("Scripting.Dictionary")
    arr = rng2.Value
    'input the larger data inside a dictionary
    For i = 1 To UBound(arr)
        For j = 1 To UBound(arr, 2)
            If Not Matches.Exists(arr(i, j)) Then Matches.Add arr(i, j), 1
        Next j
    Next i

    'input the shorter data inside an array
    arr = rng1.Value
    For i = 1 To UBound(arr)
        For j = 1 To UBound(arr, 2)
            If Matches.Exists(arr(i, j)) Then
                'your code if the value is found
                cpt = cpt + 1
            End If
        Next j
    Next i
Debug.Print "Using Damian Method : " & cpt & " Timer : " & (MicroTimer - dTime) * 1000
End Sub
TourEiffel
  • 4,034
  • 2
  • 16
  • 45
  • 1
    This is time consuming, every execution of match will tax way too much the execution time. – Damian Sep 27 '19 at 10:27
  • 1
    But op is asking for a large amount of data... You can't use any of the methods suggested on your answer... Even though they are talking about arrays, they are comparing with a single string, not finding which is useless in this case. – Damian Sep 27 '19 at 10:47
  • 2
    You have 50-100k of data to match in a data base of let's say 500k. So you need to `Application.Match` 50 to 100k times... It's insane. Instead fill a dictionary with the 500k of data, put the 50-100k of data to find and use the `Dictionary.Exists` you'll see which one is faster. – Damian Sep 27 '19 at 10:50
  • FWIW: I ran a sample on 30000 lines, and this method took almost a minute longer than dictionary or arraylist (1 minute, against under 10 seconds). Possibly faster on smaller sets as you state, however I haven't tested that scenario out. :). – JvdV Sep 27 '19 at 11:11
  • @Dorian could you provide the code for your tests please? – Damian Sep 27 '19 at 12:39
  • @Damian I added it, I also corrected a Typo in the code you gave me as edit (Check if I didn't do a mistake). Kindest regards – TourEiffel Sep 27 '19 at 12:42