Соответствие значений в массиве строк
Проблема: поиск более эффективного способа нахождения точного совпадающего значения в массиве 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
Ответы
Ответ 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
Ответ 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 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.
Тем не менее, если вам нужен код для замены найденных значений другим, я бы использовал первое решение, но вам придется немного его изменить.
Ответ 3
"Более эффективный способ (по сравнению с Application.Match
) определения того, существует ли строковое значение в массиве":
Я считаю, что нет более эффективного способа, чем тот, который вы используете, т.е. Application.Match
.
Массивы обеспечивают эффективный доступ к любому элементу, если мы знаем индекс этого элемента. Если мы хотим что-либо сделать по значению элемента (даже проверяя, существует ли элемент), мы должны отсканировать все элементы массива в худшем случае. Поэтому в худшем случае нужны сравнения элементов n
, где n
- размер массива. Поэтому максимальное время, которое нам нужно найти, если элемент существует, является линейным по размеру ввода, т.е. O(n)
. Это относится к любому языку, который использует обычные массивы.
Единственный случай, когда мы можем быть более эффективными, - это когда массив имеет специальную структуру. Для вашего примера, если элементы массива отсортированы (например, в алфавитном порядке), тогда нам не нужно сканировать весь массив: мы сравниваем его с средним элементом, а затем сравниваем с левой или правой частью массива (двоичный поиск). Но без какой-либо особой структуры нет надежды.
Dictionary/Collection
, когда вы указываете, предлагает постоянный ключ доступа к своим элементам (O(1)
). Что, возможно, не очень хорошо документировано, так это то, что можно также иметь индексный доступ к элементам словаря (Ключи и элементы): порядок, в котором элементы введены в Dictionary
, сохраняется. Их основным недостатком является то, что они используют больше памяти, поскольку для каждого элемента хранятся два объекта.
Чтобы обернуть, хотя If Not IsError(Excel.Application.match(...))
выглядит глупо, он по-прежнему более эффективен (по крайней мере, в теории). По вопросам разрешения мои знания очень ограничены. В зависимости от приложения-хоста всегда есть некоторые Find
-типы (C++
имеет Find
и find_if
например).
Я надеюсь, что это поможет!
Edit
Я хотел бы добавить пару мыслей, прочитав измененную версию сообщения и ответ Тима. В приведенном выше тексте основное внимание уделяется теоретической сложности времени различных структур данных и игнорируется проблемы реализации. Я думаю, что дух вопроса был скорее "с учетом определенной структуры данных (массива)", что является наиболее эффективным способом на практике проверки существования.
С этой целью ответ Тима является открытием.
Обычное правило "если VBA
может сделать это для вас, то не пишите его снова самостоятельно" не всегда верно. Простые операции, такие как циклизация и сравнение, могут быть быстрее, чем "соглашаться" VBA
. Две интересные ссылки здесь и здесь.