关于制作像FIND函数一样的MATCH函数

时间:2016-07-25 15:03:23

标签: excel vba excel-vba excel-formula

我正在尝试让MATCH函数像FIND函数一样工作。首先,我生成用于测试的虚拟数据。这是我使用的例程:

Sub Data_Generator()
Randomize
Dim Data(1 To 100000, 1 To 1)
Dim p As Single
For i = 1 To 100000
    p = Rnd()
    If p < 0.4 Then
        Data(i, 1) = "A"
    ElseIf p >= 0.4 And p <= 0.7 Then
        Data(i, 1) = "B"
    Else
        Data(i, 1) = "C"
    End If
Next i
Range("A1:A100000") = Data
End Sub

现在,我创建一个子例程来查找范围Data中的字符串 A 。我在这里使用两种使用MATCH函数的方法。第一种方法是重置查找数组的范围,如下面的代码:

Sub Find_Match_1()
T0 = Timer
Dim i As Long, j As Long, k As Long, Data As Range
Dim Output(1 To 100000, 1 To 1)
On Error GoTo Finish
Do
    Set Data = Range(Cells(j + 1, 1), "A100000")   'Reset the range of lookup array
    i = WorksheetFunction.Match("A", Data, 0)
    j = j + i
    Output(j, 1) = j                               'Label the position of A
    k = k + 1                                      'Counting the number of [A] found
Loop
Finish:
    Range("B1:B100000") = Output
    InputBox "The number of [A] found are " & k & " in", "Process is complete", Timer - T0
End Sub

对于第二种方法,我通过值vbNullString指定 A 所在范围的单元格,而不是重置Range("A1:A100000")。想法是在找到之后删除字符串 A ,并期望MATCH函数在Range("A1:A100000")中找到下一个字符串 A 。以下是实现第二种方法的代码:

Sub Find_Match_2()
T0 = Timer
Dim n As Long, i As Long, j As Long
Dim Data_Store()
Dim Output(1 To 100000, 1 To 1)
Data_Store = Range("A1:A100000")
On Error GoTo Finish
Do
    j = WorksheetFunction.Match("A", Range("A1:A100000"), 0)
    Output(j, 1) = j
    Cells(j, 1) = vbNullString
    n = n + 1
Loop
Finish:
Range("A1:A100000") = Data_Store
Range("B1:B100000") = Output
InputBox "The number of [A] found  are " & n & " in", "Process is complete", Timer - T0
End Sub

目标是确定哪种方法在其性能中采用MATCH功能更好。事实证明,第一种方法只完成不到0.4秒,同时第二种方法在我的电脑上完成大约一分钟。所以我的问题是:

  1. 为什么第二种方法需要很长时间才能完成?
  2. 如何改善第二种方法的性能?
  3. 可以在数组中使用MATCH函数吗?

1 个答案:

答案 0 :(得分:1)

我同意这更像是一个Code Review问题,但我选择考虑自己的好奇心,所以我会分享我发现的内容。

我认为你正在尝试N和N ^ 2计算复杂性的非常经典的案例。看看你看起来非常相似的两种方法,并考虑它们实际上在做什么,记住当你使用Match_type = 0时,MATCH函数可能只是一个线性搜索(因为你的数据是未分类的,而其他的匹配类型可以对已排序的数据进行二进制搜索。)

方法1:

  • 从A1
  • 开始
  • 继续向下行进,直到&#34; A&#34;找到了
  • 重启MATCH下面的单元格

方法2:

  • 从A1
  • 开始
  • 继续向下行进,直到&#34; A&#34;找到了
  • 清除&#34; A&#34;
  • 重新启动A1

很明显,当一种方法不断缩小它搜索的范围时,另一种方法总是从第一个单元开始并搜索整个范围。这将解决一些加速问题,并且已经将方法1提升到一个很好的领先地位,但它几乎不是完整的故事。

真正的关键在于Match必须为每种情况做的工作量。因为它的范围不断缩小并且从列表开始进一步向下移动,无论方法1的匹配从哪个单元开始,它只需要在它到达A之前搜索少量单元并重新开始外循环。同时,方法2不断地摧毁A,使它们越来越不密集,并且在获得任何命中之前强迫自己搜索越来越多的范围。最后,方法2在找到下一个A之前循环通过近100,000个空单元/ B / C。

所以平均而言,方法1的匹配每次只查看几个单元格,而方法2的匹配随着时间的推移越来越长,直到它被迫循环通过整个范围。最重要的是,方法2正在对单元格值进行大量写入,这比您在必须执行数万次时所想的要慢。

老实说,你最好的选择就是自己循环一下细胞,寻找A并随时处理它们。 MATCH没有给表带来任何好处,方法1基本上只是我描述的循环的一个更复杂的版本。

我写下这样的话:

Sub Find_Match_3()
    T0 = Timer
    Dim k As Long, r As Range
    Dim Output(1 To 100000, 1 To 1)
    For Each r In Range("A1:A100000").Cells
        If r.Value = "A" Then
            Output(r.Row, 1) = r.Row        'Label the position of A
            k = k + 1                       'Counting the number of [A] found
        End If
    Next

    Range("B1:B100000") = Output
    InputBox "The number of [A] found are " & k & " in", "Process is complete", Timer - T0
End Sub

我的机器上的速度提高了约30%。