【问题标题】:Matching values in string array匹配字符串数组中的值
【发布时间】:2013-09-16 05:44:57
【问题描述】:

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

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

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

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

这将返回一个精确匹配索引,显然受到Match 函数的限制,该函数只能在此上下文中找到 first 匹配值。这是一种常用的方法,我也用了很久。

这对于 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

【问题讨论】:

  • 如果您的数组可以有重复项,那么您可能会选择循环遍历数组的最后一个选项。如果你把它包装在一个函数中,真的不会太麻烦。我想最好的方法将取决于要检查的数组的大小以及需要进行多少次查找之类的事情。如果您需要快速查找速度,则可以将数组加载到字典中,并通过将索引数组存储为“值”来计算重复项。
  • 嘿@TimWilliams 对不起,我说得不够清楚——我测试的是布尔值;数组中是否存在值。具体来说,有没有比依赖Excel.Application.Match 更好的方法来执行此操作,例如在 Word 或 PowerPoint 等中?
  • 如果数组是一维的(或操作为一维),那么您可以将数组Join 转换为字符串并使用Instr 来测试是否存在匹配。它应该很快,但尚未测试是否比 Match 更快。
  • @ooo 是的,这是我有时使用的另一种方法,但我也没有测试过它的速度。

标签: arrays vba excel data-structures


【解决方案1】:

如果我们要谈论性能,那么运行某些测试是无可替代的。根据我的经验,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 

【讨论】:

  • 我在i = 4426 停止了第二个循环,这几乎是 2 分钟,而执行 100000 次的函数循环大约需要 7 秒。我想这就解决了。
  • 看看你什么时候可以添加这个:) t = Timer: x = Join(arr, ","): For i = 1 To 100000: b = InStr(1, x, "Value_50", vbBinaryCompare): Next i: Debug.Print "Binary", Timer - t
  • 又快了,但需要一些调整,以使“Value_5”不匹配“Value_50”。
  • 这就是分隔符派上用场的地方Value_5,
  • 如果数组已经作为 VBA 数组存在,则数组方法比使用 .MATCH 更快,因为每次调用 .MATCH 都会导致将数组传输到 .MATCH 可以处理的对象的大量开销。如果数据是 Excel 范围,则使用 .MATCH 会快得多(避免数据转换开销)
【解决方案2】:

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

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

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 的特殊性,它将只返回一个结果。

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

【讨论】:

  • 谢谢,但我应该澄清一下我没有在 Excel 中执行此操作,特别是因为我没有在 Excel 中执行此操作,所以我不能使用任何使用 Worksheet 对象的方法。无论如何,我不确定这段代码如何比Match 函数更有效——我感兴趣的是数组中是否存在值,这只是一个@ 987654325@.
  • If InStr(1, myString, strLookFor) = 0 Then 做点什么...?
【解决方案3】:

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

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

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

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

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

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

希望对你有帮助!

编辑

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

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

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

【讨论】:

  • “这里没有比您使用的方法更有效的方法了,即 Application.Match”——海报指定了一种不依赖于 Excel 特定功能的解决方案。
猜你喜欢
  • 2022-10-14
  • 2011-07-22
  • 1970-01-01
  • 2016-01-15
  • 2014-02-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-21
相关资源
最近更新 更多