Arrays 字符串数组中的匹配值

Arrays 字符串数组中的匹配值,arrays,vba,excel,data-structures,Arrays,Vba,Excel,Data Structures,问题:寻找一种更有效的方法来确定1d数组中是否存在精确的匹配值——本质上是一个布尔值true/false 我是否忽略了一些显而易见的东西?或者我只是使用了错误的数据结构,在我可能应该使用集合对象或字典时使用了数组?在后者中,我可以分别检查.Contains或.Exists方法 在Excel中,我可以检查向量数组中的值,如: If Not IsError(Application.Match(strSearch, varToSearch, False)) Then ' Do stuff End If

问题:寻找一种更有效的方法来确定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(...))
不过,这似乎很愚蠢,而且由于权限/信任中心等原因,很难对分布式文件进行管理

我已尝试使用该功能:

 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

我过去一直在寻找最好的替代方案。它也应该适用于简单的查找

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

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
Sub find_strings_2()
Dim ArrayCh()作为变量
调光范围
将第一个地址设置为字符串
作为整数的Dim i
ArrayCh=要查找的数组(“a”、“b”、“c”)字符串
使用ActiveSheet.Cells
对于i=LBound(ArrayCh)到UBound(ArrayCh)
Set c=.Find(What:=ArrayCh(i),LookAt:=xlPart,LookIn:=xlValues)
如果不是,那么c什么都不是
firstAddress=c.Address'稍后用于验证是否在同一地址上循环
做
'_____
'您的代码,其中使用“c”执行某些操作'
'这是一个范围变量,
'例如,您可以获取它的地址:
调试.打印阵列(i)和“&c.地址”示例
'_____
集合c=.FindNext(c)
循环而不是c为Nothing,c.Address为firstAddress
如果结束
接下来我
以
端接头
请记住,如果一个单元格中有多个已搜索字符串实例,则由于FindNext的特定属性,它将只返回一个结果

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

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


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

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

唯一可以提高效率的情况是,阵列具有特殊结构。例如,如果数组的元素已排序(例如,按字母顺序排序),则不需要扫描所有数组:我们将与中间元素进行比较,然后与数组的左侧或右侧部分进行比较。但是,如果不采用任何特殊结构,则没有希望

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

总而言之,尽管iError(Excel.Application.match(…)
看起来很傻,但它仍然是更有效的方法(至少在理论上)。关于权限问题,我的知识非常有限。根据主机应用程序的不同,总有一些
查找
类型的函数(例如,
C++
具有
find
find_if

我希望这有帮助

编辑

在阅读了文章的修订版和Tim的答案后,我想补充几点想法。上面的文本主要关注各种数据结构的理论时间复杂性,而忽略了实现问题。我认为问题的实质是“给定某个数据结构(数组)”在实践中,什么是检查存在最有效的方法

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


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

如果我们要讨论性能,那么运行一些测试就没有子例程。根据我的经验,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 
如果你的ar