Есть ли в VBA структура словаря?

Ответы:

343

Да.

Установите ссылку на среду выполнения сценариев MS («среда выполнения сценариев Microsoft»). В соответствии с комментарием @ regjo, перейдите в Tools-> References и поставьте галочку для «Microsoft Scripting Runtime».

Окно ссылок

Создайте экземпляр словаря, используя код ниже:

Set dict = CreateObject("Scripting.Dictionary")

или

Dim dict As New Scripting.Dictionary 

Пример использования:

If Not dict.Exists(key) Then 
    dict.Add key, value
End If 

Не забудьте установить словарь, Nothingкогда вы закончили его использовать.

Set dict = Nothing 
Митч Пшеничный
источник
17
Этот тип структуры данных предоставляется средой выполнения сценариев, а не VBA. В принципе, VBA может использовать практически любой тип структуры данных, доступный для него через интерфейс COM.
Дэвид-У-Фентон
164
Просто для полноты: вам нужно обратиться к «Microsoft Scripting Runtime», чтобы это работало (перейдите в Tools-> References) и отметьте его.
Regjo
7
Коллекции VBA имеют ключ. Но, возможно, у нас есть другое определение keyed.
Дэвид-У-Фентон
8
Я использую Excel 2010 ... но без ссылки на «Среда выполнения сценариев Microsoft» - см. Просто создание CreateObject НЕ работает. Итак, @masterjo, я думаю, что ваш комментарий выше неверен. Если я что-то упустил .. Так, ребята Инструменты -> ссылки не требуется.
ihightower
4
Как к сведению, вы не можете использовать Dim dict As New Scripting.Dictionaryбез ссылки. Без ссылки вы должны будете использовать метод поздней привязки CreateObjectдля создания экземпляра этого объекта.
Дэвид Земенс
182

VBA имеет объект коллекции:

    Dim c As Collection
    Set c = New Collection
    c.Add "Data1", "Key1"
    c.Add "Data2", "Key2"
    c.Add "Data3", "Key3"
    'Insert data via key into cell A1
    Range("A1").Value = c.Item("Key2")

В Collectionобъекте выполняет на основе ключей поисков с использованием хэша так быстро.


Вы можете использовать Contains()функцию, чтобы проверить, содержит ли определенная коллекция ключ:

Public Function Contains(col As Collection, key As Variant) As Boolean
    On Error Resume Next
    col(key) ' Just try it. If it fails, Err.Number will be nonzero.
    Contains = (Err.Number = 0)
    Err.Clear
End Function

Изменить 24 июня 2015 года : короче Contains()благодаря @TWiStErRob.

Редактировать 25 сентября 2015 : добавлено Err.Clear()благодаря @scipilot.

Калеб Хаттинг
источник
5
Хорошо сделано для указания на встроенный объект Collection, который можно использовать в качестве словаря, поскольку метод Add имеет необязательный аргумент «ключ».
Саймон Тьюси
9
Недостаток объекта коллекции в том, что вы не можете проверить, есть ли ключ в коллекции. Это просто выдаст ошибку. Это большая вещь, мне не нравятся коллекции. (я знаю, что есть обходные пути, но большинство из них "некрасивые")
MiVoth
5
Обратите внимание, что поиск строковых ключей (например, c.Item ("Key2")) в словаре VBA IS хэширован, но поиск по целочисленному индексу (например, c.Item (20)) не является - это линейный для / следующий стиль поиска и его следует избегать. Лучше всего использовать коллекции только для поиска по строковому ключу или для каждой итерации.
Бен Макинтайр
4
Я нашел короче Contains: On Error Resume Next_ col(key)_Contains = (Err.Number = 0)
TWiStErRob
5
Возможно, функция должна быть названа ContainsKey; кто-то, читающий только вызов, может спутать его с проверкой того, что он содержит определенное значение.
jpmc26
44

VBA не имеет внутренней реализации словаря, но из VBA вы все еще можете использовать объект словаря из MS Scripting Runtime Library.

Dim d
Set d = CreateObject("Scripting.Dictionary")
d.Add "a", "aaa"
d.Add "b", "bbb"
d.Add "c", "ccc"

If d.Exists("c") Then
    MsgBox d("c")
End If
Ярмо
источник
29

Дополнительный пример словаря, который полезен для сдерживания частоты встречаемости.

Вне цикла:

Dim dict As New Scripting.dictionary
Dim MyVar as String

Внутри цикла:

'dictionary
If dict.Exists(MyVar) Then
    dict.Item(MyVar) = dict.Item(MyVar) + 1 'increment
Else
    dict.Item(MyVar) = 1 'set as 1st occurence
End If

Чтобы проверить частоту:

Dim i As Integer
For i = 0 To dict.Count - 1 ' lower index 0 (instead of 1)
    Debug.Print dict.Items(i) & " " & dict.Keys(i)
Next i
Джон М
источник
1
Дополнительная учебная
Джон М
Это был очень хороший ответ, и я использовал его. Однако я обнаружил, что не могу ссылаться на dict.Items (i) или dict.Keys (i) в цикле, как вы. Мне пришлось хранить их (список элементов и список ключей) в отдельных переменных перед входом в цикл, а затем использовать эти переменные, чтобы получить нужные мне значения. Like - allItems = companyList.Items allKeys = companyList.Keys allItems (i) Если нет, я получил бы ошибку: «Процедура свойства let не определена, а процедура get свойства не возвращала объект» при попытке доступа к ключам (i) или Элементы (я) в цикле.
Раддевус
10

Основываясь на ответе cjrh , мы можем создать функцию Contains, не требующую меток (я не люблю использовать метки).

Public Function Contains(Col As Collection, Key As String) As Boolean
    Contains = True
    On Error Resume Next
        err.Clear
        Col (Key)
        If err.Number <> 0 Then
            Contains = False
            err.Clear
        End If
    On Error GoTo 0
End Function

Для моего проекта я написал набор вспомогательных функций, чтобы Collectionповедение стало более похожим на a Dictionary. Это все еще позволяет рекурсивные коллекции. Вы заметите, что Key всегда стоит первым, потому что он был обязательным и имел больше смысла в моей реализации. Я также использовал только Stringключи. Вы можете изменить его обратно, если хотите.

Устанавливать

Я переименовал это, чтобы установить, потому что это перезапишет старые значения.

Private Sub cSet(ByRef Col As Collection, Key As String, Item As Variant)
    If (cHas(Col, Key)) Then Col.Remove Key
    Col.Add Array(Key, Item), Key
End Sub

Получить

Материал errпредназначен для объектов, так как вы передаете объекты с использованием setи без переменных. Я думаю, что вы можете просто проверить, если это объект, но я был вынужден на время.

Private Function cGet(ByRef Col As Collection, Key As String) As Variant
    If Not cHas(Col, Key) Then Exit Function
    On Error Resume Next
        err.Clear
        Set cGet = Col(Key)(1)
        If err.Number = 13 Then
            err.Clear
            cGet = Col(Key)(1)
        End If
    On Error GoTo 0
    If err.Number <> 0 Then Call err.raise(err.Number, err.Source, err.Description, err.HelpFile, err.HelpContext)
End Function

имеет

Причина этого поста ...

Public Function cHas(Col As Collection, Key As String) As Boolean
    cHas = True
    On Error Resume Next
        err.Clear
        Col (Key)
        If err.Number <> 0 Then
            cHas = False
            err.Clear
        End If
    On Error GoTo 0
End Function

удалять

Не бросает, если его не существует. Просто убедитесь, что он удален.

Private Sub cRemove(ByRef Col As Collection, Key As String)
    If cHas(Col, Key) Then Col.Remove Key
End Sub

Ключи

Получить массив ключей.

Private Function cKeys(ByRef Col As Collection) As String()
    Dim Initialized As Boolean
    Dim Keys() As String

    For Each Item In Col
        If Not Initialized Then
            ReDim Preserve Keys(0)
            Keys(UBound(Keys)) = Item(0)
            Initialized = True
        Else
            ReDim Preserve Keys(UBound(Keys) + 1)
            Keys(UBound(Keys)) = Item(0)
        End If
    Next Item

    cKeys = Keys
End Function
Эван Кеннеди
источник
6

Кажется, что в скриптовом словаре времени выполнения есть ошибка, которая может испортить ваш дизайн на продвинутых этапах.

Если значение словаря является массивом, вы не можете обновить значения элементов, содержащихся в массиве, посредством ссылки на словарь.

Kalidas
источник
6

Да. Для VB6 , VBA (Excel) и VB.NET

Мэтью Флэшен
источник
2
Вы можете прочитать вопросы еще: я спросил о VBA: Visual Basic для приложений, а не для VB, не для VB.Net, не для любого другого языка.
1
fessGUID: опять же, вы должны читать ответы больше! Этот ответ также может быть использован для VBA (в частности, первая ссылка).
Конрад Рудольф
5
Я признаю. Я читаю вопрос слишком быстро. Но я сказал ему, что ему нужно было знать.
Мэтью Флэшен
5
@ Oorang, нет абсолютно никаких доказательств того, что VBA становится подмножеством VB.NET, правил обратного компоновки в Office - представьте, что вы пытаетесь преобразовать каждый макрос Excel, когда-либо написанный.
Ричард Гадсден
2
VBA - это СУПЕРСЕТ VB6. Он использует ту же основную DLL, что и VB6, но затем добавляет все виды функциональности для конкретных приложений в Office.
Дэвид-У-Фентон,
4

Если по какой-либо причине вы не можете установить дополнительные функции в Excel или не хотите, вы также можете использовать массивы, по крайней мере, для простых задач. В качестве WhatIsCapital вы вводите название страны, а функция возвращает вам ее капитал.

Sub arrays()
Dim WhatIsCapital As String, Country As Array, Capital As Array, Answer As String

WhatIsCapital = "Sweden"

Country = Array("UK", "Sweden", "Germany", "France")
Capital = Array("London", "Stockholm", "Berlin", "Paris")

For i = 0 To 10
    If WhatIsCapital = Country(i) Then Answer = Capital(i)
Next i

Debug.Print Answer

End Sub
user2604899
источник
1
Концепция этого ответа обоснована, но пример кода не будет работать так, как написано. Каждая переменная нуждается в своем собственном Dimключевом слове Countryи Capitalдолжна быть объявлена ​​как Варианты из-за использования Array(), iдолжна быть объявлена ​​(и должна быть, если Option Explicitона установлена), и счетчик цикла будет выбрасывать ошибку выхода за границы - безопаснее использовать UBound(Country)для Toзначения. Также, возможно, стоит отметить, что хотя Array()функция является полезным сочетанием клавиш, это не стандартный способ объявления массивов в VBA.
января
3

Все остальные уже упоминали об использовании версии scripting.runtime класса Dictionary. Если вы не можете использовать эту DLL, вы также можете использовать эту версию, просто добавьте ее в свой код.

https://github.com/VBA-tools/VBA-Dictionary/blob/master/Dictionary.cls

Это идентично версии Microsoft.

Михель ван дер Блонк
источник