匹配字符串数组中的值

时间:2013-09-12 01:54:40

标签: arrays vba excel-vba data-structures excel

问题:寻找一种更有效的方法来查找1d数组中是否存在完全匹配的值 - 基本上是布尔值true/false

我是否忽略了一些明显的东西?或者我只是使用错误的数据结构,当我可能应该使用集合对象或字典时使用数组?在后者中,我可以分别检查.Contains.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

3 个答案:

答案 0 :(得分:29)

如果我们要讨论性能,那么运行一些测试就没有任何偏见。根据我的经验,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 

答案 1 :(得分: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的具体情况,它只返回一个结果。

但是,如果你需要一个用另一个代替替换找到的值的代码,我会使用第一个解决方案,但你必须稍微改一下。

答案 2 :(得分:1)

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

我相信没有比您使用的方式更有效的方式,即Application.Match

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

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

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

总结虽然If Not IsError(Excel.Application.match(...))看起来很愚蠢,但它仍然是更有效的方式(至少在理论上)。在许可问题上,我的知识非常有限。根据主机应用程序的不同,总会有一些Find类型的函数(例如C++findfind_if)。

我希望有所帮助!

修改

在阅读修改后的帖子和Tim的回答后,我想补充一些想法。上述文本侧重于各种数据结构的理论时间复杂性,忽略了实现问题。我认为问题的精神是“给定一定的数据结构(数组)”,在实践中检查存在的最有效方法是什么。

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

传统规则“如果VBA可以为你做,那么不要自己再写”并非总是如此。循环和比较等简单操作可以比“同意”VBA函数更快。两个有趣的链接是binary search)here