18

问题:寻找一种更有效的方法来查找一维数组中是否存在精确匹配的值——本质上是一个 boolean true/false

我是否忽略了一些明显的东西?还是我只是使用了错误的数据结构,当我可能应该使用集合对象或字典时使用数组?在后者中,我可以分别检查.Containsor.Exists方法

在 Excel 中,我可以检查向量数组中的值,例如:

If Not IsError(Application.Match(strSearch, varToSearch, False)) Then
' Do stuff
End If

这将返回一个完全匹配索引,显然受限于仅在此上下文中Match找到第一个匹配值的函数的限制。这是一种常用的方法,我也用了很长时间。

这对于 Excel 来说已经足够令人满意了——但是其他应用程序呢?

在其他应用程序中,我可以做基本相同的事情,但需要启用对 Excel 对象库的引用,然后:

   If Not IsError(Excel.Application.match(...))

但是,这似乎很愚蠢,并且由于权限/信任中心/等原因,很难对分布式文件进行管理。

我尝试使用Filter()函数:

 If Not Ubound(Filter(varToSearch, strSearch)) = -1 Then
    'do stuff
 End If

但是这种方法的问题是Filter返回一个部分匹配的数组,而不是一个完全匹配的数组。(我不知道为什么返回子字符串/部分匹配会很有用。)

另一种选择是逐字迭代数组中的每个值(我认为这也是非常常用的)——这似乎比调用 Excel 的Match函数更加不必要的麻烦。

For each v in vArray
   If v = strSearch Then
    ' do stuff
   End If
Next
4

3 回答 3

30

如果我们要谈论性能,那么运行一些测试是无可替代的。根据我的经验,Application.Match() 比调用使用循环的函数慢十倍。

Sub Tester()

    Dim i As Long, b, t
    Dim arr(1 To 100) As String

    For i = 1 To 100
        arr(i) = "Value_" & i
    Next i

    t = Timer
    For i = 1 To 100000
        b = Contains(arr, "Value_50")
    Next i
    Debug.Print "Contains", Timer - t

    t = Timer
    For i = 1 To 100000
        b = Application.Match(arr, "Value_50", False)
    Next i
    Debug.Print "Match", Timer - t

End Sub


Function Contains(arr, v) As Boolean
Dim rv As Boolean, lb As Long, ub As Long, i As Long
    lb = LBound(arr)
    ub = UBound(arr)
    For i = lb To ub
        If arr(i) = v Then
            rv = True
            Exit For
        End If
    Next i
    Contains = rv
End Function

输出:

Contains       0.8710938 
Match          4.210938 
于 2013-09-12T16:06:27.917 回答
1

Application.Match“查找字符串值是否存在于数组中的更有效方法(与 相比)”:

我相信没有比您正在使用的方法更有效的方法了,即Application.Match.

如果我们知道该元素的索引,则数组允许有效访问任何元素。如果我们想通过元素值做任何事情(甚至检查一个元素是否存在),我们必须在最坏的情况下扫描数组的所有元素。因此,最坏的情况需要n元素比较,其中n是数组的大小。因此,我们需要查找元素是否存在的最长时间与输入的大小成线性关系,即O(n). 这适用于任何使用传统数组的语言。

我们可以提高效率的唯一情况是数组具有特殊结构。对于您的示例,如果数组的元素已排序(例如按字母顺序),那么我们不需要扫描所有数组:我们与中间元素进行比较,然后与数组的左侧或右侧部分进行比较(二进制搜索) . 但是不假设任何特殊结构,就没有希望了。。

正如您所指出的,它提供了对其元素 ( )的Dictionary/Collection持续键访问。O(1)可能没有很好记录的是,人们也可以对字典元素(键和项)进行索引访问Dictionary:保留元素输入到字典中的顺序。它们的主要缺点是它们使用更多内存,因为每个元素都存储了两个对象。

总结一下,虽然If Not IsError(Excel.Application.match(...))看起来很傻,但它仍然是更有效的方法(至少在理论上)。在许可问题上,我的知识非常有限。根据宿主应用程序,总有一些Find-type 函数(例如C++hasfind和)。find_if

我希望这会有所帮助!

编辑

在阅读了帖子的修订版和蒂姆的回答后,我想补充一些想法。上面的文字关注的是各种数据结构的理论时间复杂度,而忽略了实现问题。我认为这个问题的精神是“给定特定的数据结构(数组)”,在实践中检查存在的最有效方法是什么。

为此,蒂姆的回答令人大开眼界。

传统的规则“如果VBA可以为你做,那么不要自己再写”并不总是正确的。像循环和比较这样的简单操作可以比“同意”VBA函数更快。这里这里有两个有趣的链接。

于 2013-09-12T09:08:02.010 回答
1

我曾经寻找最佳的替换解决方案。它也应该适用于简单的查找。

要查找字符串的第一个实例,您可以尝试使用以下代码:

Sub find_strings_1()

Dim ArrayCh() As Variant
Dim rng As Range
Dim i As Integer

 ArrayCh = Array("a", "b", "c")

With ActiveSheet.Cells
    For i = LBound(ArrayCh) To UBound(ArrayCh)
        Set rng = .Find(What:=ArrayCh(i), _
        LookAt:=xlPart, _
        SearchOrder:=xlByColumns, _
        MatchCase:=False)

        Debug.Print rng.Address

    Next i
End With

End Sub

如果您想查找所有实例,请尝试以下操作。

Sub find_strings_2()

Dim ArrayCh() As Variant
Dim c As Range
Dim firstAddress As String
Dim i As Integer

 ArrayCh = Array("a", "b", "c") 'strings to lookup

With ActiveSheet.Cells
    For i = LBound(ArrayCh) To UBound(ArrayCh)
        Set c = .Find(What:=ArrayCh(i), LookAt:=xlPart, LookIn:=xlValues)

        If Not c Is Nothing Then
            firstAddress = c.Address 'used later to verify if looping over the same address
            Do
                '_____
                'your code, where you do something with "c"
                'which is a range variable,
                'so you can for example get it's address:
                Debug.Print ArrayCh(i) & " " & c.Address 'example
                '_____
                Set c = .FindNext(c)

            Loop While Not c Is Nothing And c.Address <> firstAddress
        End If
    Next i
End With

End Sub

请记住,如果一个单元格中有多个搜索字符串的实例,由于 FindNext 的特殊性,它将只返回一个结果。

不过,如果您需要一个代码来用另一个替换找到的值,我会使用第一个解决方案,但您必须对其进行一些更改。

于 2013-09-12T08:58:04.467 回答