问题:寻找一种更有效的方法来查找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
答案 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++
有find
和find_if
)。
我希望有所帮助!
修改强>
在阅读修改后的帖子和Tim的回答后,我想补充一些想法。上述文本侧重于各种数据结构的理论时间复杂性,忽略了实现问题。我认为问题的精神是“给定一定的数据结构(数组)”,在实践中检查存在的最有效方法是什么。
为此,蒂姆的回答令人大开眼界。
传统规则“如果VBA
可以为你做,那么不要自己再写”并非总是如此。循环和比较等简单操作可以比“同意”VBA
函数更快。两个有趣的链接是binary search)和here。