Частичное существует в словаре

Я ищу способ проверить, включено ли значение в словарь Excel - VBA. Однако простой dict.Exists("") не является правильным подходом в этой ситуации, поскольку искомое значение не может точно совпадать со значением в словаре.

Например, я ищу слово apple в словаре, в котором есть фраза fruit apple. Итак, apple включен в словарь как fruit apple. Следует избегать чувствительности к регистру.

В настоящее время я зацикливаю словарь, что отнимает много времени.

Есть ли дополнения типа dict.Exists("*apple*")

Есть идеи?

Вам нужно будет добавить больше информации в вопрос; какие у вас ключи/значения, как вы их загружаете и т. д. Ресурс по словарям находится здесь: excelmacromastery.com/vba-dictionary

Tragamor 24.07.2024 12:06

Вы всегда ищете полные слова, разделенные пробелом (или чем-то еще)? Затем создайте второй словарь, который будет содержать все отдельные слова в качестве ключа и ключ первого словаря в качестве значения. Таким образом, ключ словаря fruit apple первого словаря получит 2 записи во втором словаре: одну с ключом fruit и одну с ключом apple. Оба получают fruit apple в качестве значения, поэтому вы можете использовать его как ключ в исходном словаре.

FunThomas 24.07.2024 12:07

@FunThomas Я ищу одно слово в маленькой фразе. Искомое слово может в любом случае появиться в поисковой фразе.

Error 1004 24.07.2024 12:10

Думаю, было бы полезно показать еще несколько примеров данных.

FunThomas 24.07.2024 12:15

Scripting.Dictionary ужасен для добавления большого количества пар ключ-элемент. Вместо этого используйте VBA-FastDictionary. Он также совместим с Mac.

Cristian Buse 25.07.2024 10:14
Преобразование HTML-таблицы в профессиональный документ Excel
Преобразование HTML-таблицы в профессиональный документ Excel
Это самый простой способ создания Excel из HTML-таблицы.
Импорт excel в laravel в базу данных
Импорт excel в laravel в базу данных
Здравствуйте, дорогой читатель, в этой статье я расскажу практическим и быстрым способом, как импортировать файл Excel в вашу базу данных с помощью...
4
5
99
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Метод Dictionary.Keys вернет массив, содержащий все ключи.

Функция Excel ПОИСКПОЗ позволяет выполнять поиск в массиве (в VBA ее можно назвать как Application.Match). Он поддерживает подстановочные знаки и не учитывает регистр, однако обратите внимание, что он возвращает только один результат, даже если несколько ключей в словаре соответствуют шаблону.

Объединение двух:

Dim vArr AS Variant, vMatch As Variant
vArr = dict.keys
vMatch = Application.Match("*apple*", vArr, 0)
If IsError(vMatch) Then
    MsgBox "Key Not Found", vbCritical
Else
    MsgBox vArr(vMatch-1), vbInformation 'the -1 is because the Array is Zero-Indexed, but Match returns One-Indexed results
End If

Отличная попытка. Обратите внимание, что вам не нужно копировать ключи в промежуточный массив.

FunThomas 24.07.2024 12:39

Разве вам это не понадобится, например, для увеличения счетчика в значении существующего ключа? @FunThomas т. е. dict(vArr(vMatch)) = dict(vArr(vMatch)) +1

Notus_Panda 24.07.2024 12:46

это было бы dict(dict.Keys(vMatch-1)) - но для этого я бы использовал промежуточную переменную, например key = dict.Keys(vMatch-1), а затем dict(key) = dict(key) + 1

FunThomas 24.07.2024 13:41
Ответ принят как подходящий

Звучит как идеальная работа для (относительно неизвестной) Filter() функции.

Sub Test()

Dim Dict As Object: Set Dict = CreateObject("Scripting.Dictionary")

Dict.Add "Apples", "Found You!"
Dict.Add "Banana", 2
Dict.Add "Cherry", 3
Dict.Add "Date", 4
Dict.Add "Elderberry", 5

r_out = Filter(Dict.Keys, "apple", 1, 1)
If UBound(r_out) > -1 Then
    'Do something with r_out, which is an array of all matched keys
    'For example, traverse filtered keys and return values:
    For i = 0 To UBound(r_out)
        Debug.Print Dict(r_out(i))
    Next
Else
    'Something if no matches are found
    Debug.Print "Bummer"
End If

End Sub

4 параметра:

  • Одномерный массив строк, который Dict.Keys может предоставить;
  • Строка для поиска (регистронезависима, поэтому не беспокойтесь);
  • Логическое значение (или эквивалент 1), сообщающее функции, что мы хотим возвращать значения, включающие строку;
  • Целое число 1 (vbTextCompare), чтобы сообщить функции, что мы хотим сравнить текст.

Я не уверен, что ты хочешь делать дальше...

Большое спасибо за ваш ответ. Есть ли способ получить словарь Items («Нашел тебя!», 2, 3 и т. д.)?

Error 1004 24.07.2024 13:36

@ Ошибка1004, да. Я отредактировал ответ несколько минут назад, так как думал, что вы можете спросить об этом. Поскольку r_out представляет собой одномерный массив сам по себе, вы можете зациклить отфильтрованные пары ключ/значение или, как я продемонстрировал, вернуть первую пару ключ/значение > «Нашел вас!».

JvdV 24.07.2024 13:39

как я могу получить значение .Items для значений, включенных в массив r_out?

Error 1004 24.07.2024 13:51

Разве я тебе не показывал? Я что-то упускаю в вашем вопросе? Я уже упоминал, что теперь это все ключи, которые вы можете пройти в цикле for, если вас интересуют все связанные значения (элементы). @ Ошибка1004. Я отредактирую ответ, включив в него цикл.

JvdV 24.07.2024 13:58

Мне нравится ответ @JvdV, и его, вероятно, проще всего реализовать, но при этом он не зависит от приложения.

Мой ответ будет посвящен повышению скорости с помощью оператора Like, и я проведу сравнение с другими ответами.

Идея состоит в том, чтобы перебрать все ключи примерно так:

For Each tkey In Dict
    If tkey Like "*apple*" Then
       ...
    End If
Next tkey

Нам понадобится функция поддержки для адаптации шаблонов, если нам нужны сравнения без учета регистра. Например, If tkey Like "*apple*" Then чувствителен к регистру, а If tkey Like "*[Aa][Pp][Pp][Ll][Ee]*" Then нам нужен для сравнений без учета регистра.

Для адаптации шаблонов нам нужна следующая вспомогательная функция:

Public Function CaseInsensitivePattern(ByRef pattern As String) As String
    Dim chars As Long: chars = Len(pattern)
    Dim i As Long
    Dim j As Long
    Dim chL As String
    Dim chU As String
    '
    If chars = 0 Then Exit Function
    CaseInsensitivePattern = Space$(chars * 4) 'Init Buffer
    '
    j = 1
    For i = 1 To chars
        chL = LCase$(Mid$(pattern, i, 1))
        chU = UCase$(chL)
        If chL = chU Then
            Mid$(CaseInsensitivePattern, j) = chL
            j = j + 1
        Else
            Mid$(CaseInsensitivePattern, j) = "[" & chU & chL & "]"
            j = j + 4
        End If
    Next i
    CaseInsensitivePattern = Left$(CaseInsensitivePattern, j - 1)
End Function

Чтобы протестировать это на большом количестве элементов, я буду использовать VBA-FastDictionary вместо Scripting.Dictionary. Добавление 1 миллиона текстовых ключей с помощью Scripting.Dictionary на моем компьютере занимает около 23 секунд, а Fast Dictionary — всего около 0,65 секунды. Если вам нужна более подробная информация, есть раздел сравнительного анализа. Если вы не против подождать с добавлением элементов, продолжайте использовать Scripting.Dictionary.

Вот сравнение:

Sub TestSpeed()
    Dim i As Long
    Const iterations As Long = 1000000
    Dim t As Double
    Dim dict As New Dictionary
    Dim v As Variant
    Dim coll As Collection
    '
    'Add key-item pairs
    For i = 1 To iterations
        dict.Add "Key " & i, i
        If i Mod iterations \ 10 = 0 Then dict.Add "apple " & i, i
    Next i
    Debug.Print "Results in seconds"
    Debug.Print "------------------"
    '
    'Filter solution
    Dim arr As Variant
    t = Timer
    arr = Filter(dict.Keys, "test", True, vbTextCompare)
    Debug.Print "Filter on Dict.Keys: " & Round(Timer - t, 3)
    '
    'Match solution
    Dim vMatch As Variant
    t = Timer
    vMatch = Application.Match("*apple*", dict.Keys, 0)
    Debug.Print "Match on Dict.Keys: " & Round(Timer - t, 3)
    '
    'Pattern match case-sensitive
    Dim pattern As String: pattern = "*apple*"
    Set coll = New Collection
    t = Timer
    For Each v In dict
        If v Like pattern Then coll.Add v
    Next v
    Debug.Print "Pattern match case-sensitive on Dict (implicit keys): " & Round(Timer - t, 3)
    '
    'Pattern match case-insensitive
    Dim patternI As String: patternI = CaseInsensitivePattern(pattern)
    Set coll = New Collection
    t = Timer
    For Each v In dict
        If v Like patternI Then coll.Add v
    Next v
    Debug.Print "Pattern match case-insensitive on Dict (implicit keys): " & Round(Timer - t, 3)
    '
    'Pattern match case-sensitive on keys array
    Set coll = New Collection
    t = Timer
    For Each v In dict.Keys
        If v Like pattern Then coll.Add v
    Next v
    Debug.Print "Pattern match case-sensitive on Dict.Keys: " & Round(Timer - t, 3)
End Sub

На моем компьютере Win VBA7 x64 я получаю следующие результаты в окне интерпретации:

Results in seconds
------------------
Filter on Dict.Keys: 0.309
Match on Dict.Keys: 0.133
Pattern match case-sensitive on Dict (implicit keys): 0.074
Pattern match case-insensitive on Dict (implicit keys): 0.133
Pattern match case-sensitive on Dict.Keys: 0.098

Обратите внимание, что подход Match возвращает только первый найденный результат, тогда как все остальные возвращают все совпадения либо в массиве, либо в результате коллекции.

Другие вопросы по теме