Vba excel. объект dictionary (свойства, методы, примеры)

ConcurrentDictionary

Представляет потокобезопасную коллекцию пар «ключ-значение», доступ к которой могут одновременно получать несколько потоков.

Для настройки есть 2 основных параметра:

сapacity — первоначальное кол-во элементов. По умолчанию — 31.

concurrencyLevel – предполагаемое число потоков на запись. По умолчанию = 4

Методы ConcurrentDictionary.

Основные Методы словаря можно разделить на 3 группы: 

  • полностью неблокируемые;
  • блокировка одного элемента из пула блокировок;
  • блокировка всего словаря;

К полностью не блокируемым операциям можно отнести:

  • ContainsKey
  • TryGet
  • this
  • GetEnumerator – операция не обеспечивает целостность данных (не использует снепшоты), т.е. данные за время работы функции могут поменяться.

Все операции чтения (Get/ContainsKey) имеют примерно одинаковый алгоритм работы:

  • вычисление хеша ключа через GetHashCode()
  • вычисление бакета, в котором лежит наш элемент
  • сравнения значения ключа в бакете с тем, который у нас
  • чтение значения с использованием Volatile.Read

К операциям с блокировкой одного элемента из пула блокировок можно отнести:

  • TryAdd
  • TryUpdate
  • TryRemove

Ниже примерный алгоритм работы:

  • Вычисление хеша ключа нового элемента
  • Вычисление бакета bucketNo, в который будет добавлен элемент, и номера блокировки из пула
  • Блокировка bucketNo через Monitor.Enter
  • Запись элемента с использованием Volatile.Write
  • Освобождение блокировки Monitor.Exit

К самым неэффективным операциям, которые блокируют весь словарь, относятся:

  • Count, IsEmpty. Да, эти операции требуют полной блокировки словаря. Если вам необходимо сохранить в лог-файл число элементов, то можно использовать GetEnumerator и LINQ. Так же  эти методы  захватывают все локи в словаре. Лучше воздержаться от частого вызова этих свойств из нескольких потоков.
  • Keys, Values – получение списка ключей и списка значений соответственно. Они не только берут все локи, но и целиком копируют в отдельный List все ключи и значения. В отличие от традиционного Dictionary, одноимённые свойства которого возвращают «тонкие» обертки, здесь нужно быть готовым к крупным аллокациям памяти.
  • CopyTo – explicit ICollection
  • Clear, ToArray

В отличие от обычного Dictionary, можно производить вставку в ConcurrentDictionary или удаление из него прямо во время перечисления

Удалять элементы можно не только по ключу, но и по точному совпадению key + value, причем атомарно! Это недокументированная возможность, скрытая за explicit-реализацией интерфейса ICollection. Она позволяет безопасно очищать такой кэш даже в условиях гонки с обновлением значения:

в условиях конкурентного доступа GetOrAdd может вызвать делегат-фабрику для одного ключа сильно больше одного раза. Если так делать нельзя или дорого, достаточно обернуть значение в Lazy:

Overview

One of the nice features of other scripting languages, such as Perl, LISP, and Python is what is called an associative array. A n associative array differs from a “normal” array in one major way: rather than being indexed numerically (i.e. 0, 1, 2, 3, …), it is indexed by a key, or an English-like word. VBScript has something very similar to an associative array. This object is called the Dictionary object. The VBScript Dictionary object provides an item indexing facility. Dictionaries are part of Microsoft’s Script Runtime Library.

The Dictionary object is used to hold a set of data values in the form of (key, item) pairs. A dictionary is sometimes called an associative array because it associates a key with an item. The keys behave in a way similar to indices in an array, except that array indices are numeric and keys are arbitrary strings. Each key in a single Dictionary object must be unique.

Dictionaries are frequently used when some items need to be stored and recovered by name. For example, a dictionary can hold all the environment variables defined by the system or all the values associated with a registry key. However, a dictionary can only store one item for each key value. That is, dictionary keys must all be unique.

Creating Dictionaries

To construct an instance of a dictionary object, just use the following lines of code:

Dictionary objects have one property that should be set before any data values are stored in the dictionary. There are two modes for the .CompareMode property which control how individual keys are compared. If the mode is (the default), upper and lower case letters in keys are considered distinct. If the mode is , upper and lower case letters in keys are considered identical. This means that a Dictionary object in binary mode can contain two keys “Key” and “key”, whereas these would be considered the same key in text mode.

Наполнение словаря

4.1. Типы данных ключа и элемента

Dictionary наполняется по одному элементу. Не существует способов наполнить словарь массово. Чтобы добавить в словарь новый элемент вы должны иметь уникальный ключ и сам элемент, который под этим ключом будет храниться в словаре.

В качестве типа данных для элемента может быть использовано практически всё что угодно: числа, логический тип, строки (в том числе пустые), дата-время, массивы, любые объекты (листы, диапазоны, коллекции, другие словари, пустой указатель Nothing ).

В качестве типа данных для ключа могут быть использованы: числа, строки, дата-время, объекты, но не массивы.

UDT (User Defined Type) не может напрямую использоваться в качестве ключа и/или элемента, но данное ограничение можно обойти, объявив аналог UDT , создав класс и определив в нём свойства аналогичные имеющимся в UDT . А поскольку класс — это объектный тип, то его уже можно использовать для ключей и элементов.

4.2. Через метод Add

На листе Example , прилагаемого к статье файла, есть таблица с TOP30 стран по площади их территории. Для области данных этой таблицы объявлен именованный диапазон SquareByCountry . Пример ниже добавляет все строки указанногот ИД в Dictionary по принципу страна ( key ) — площадь ( item ):

Как видите, для добавления элемента (item) мы в 12-й строке кода использовали метод Add объекта dicCountry . Если в нашей таблице будет задвоена страна, то при попытке добавить в словарь элемента с ключом, который в словаре уже есть, будет сгенерировано исключение:

4.3. Через свойство Item

Используя свойство Item , также можно добавлять пары ключ-элемент, однако, при попытке добавить дублирующий ключ исключения сгенерировано НЕ БУДЕТ , а элемент будет заменён на новый (с потерей старого). Это очень полезно — иметь возможность выбирать способы наполнения словаря, отличающиеся реакцией на задвоение ключей.

4.4. Неявное добавление ключа в Dictionary

И ещё один неожиданный и я бы сказал экзотический способ пополнения словаря. Если упомянуть свойство Item по ПРАВУЮ сторону оператора присваивания, то он оказывается добавит в словарь key с пустым item, если данного key не существует в коллекции. Если же такой key уже существует, то никаких действий предпринято не будет.

Ещё раз хочу обратить ваше внимание, что элемент (item) при таком пополнении коллекции будет пустым ( Empty ). Это можно использовать, если вам нет необходимости что-то хранить в элементах в качестве полезной нагрузки (например, когда вы просто строите список уникальных значений, встречающихся в столбце таблицы)

Если вы читаете словарь через Item (а это, собственно, самый логичный и распространенный метод), и при этом хотите избежать добавления пустых ключей в словарь, используйте предварительно метод Exists , что контроля наличия такого ключа в коллекции.

OrderedDictionary

Иногда бывают моменты когда вы хотите использовать ключи для поиска или foreach для итерации с помощью DictionaryEntry объектов. Элементы OrderedDictionary доступны с помощью ключа или индекса .Элементы OrderedDictionary не сортируются по ключу, в отличие от элементов класса который мы рассматриваем выше. 

Вы можете видеть, что я использовал два разных метода, чтобы перебрать коллекцию. В первом примере я использовал цикл for и числовой индекс для извлечения каждого объекта (в данном случае строки), хранящегося в коллекции. 

Другим преимуществом является скорость. При просмотре большой коллекции чтение OrderedDictionary с использованием первого примера, числового индекса, всегда будет быстрее, чем при использовании метода стиля словаря.

Когда вам нужна «мощь» коллекции и простой доступ к числовому индексу, OrderedDictionary является предпочтительной коллекцией.

Performance: Dictionary vs. Collection

ceteris paribus
In an Excel worksheet, I made 50 columns, each with 10,000 random integers.  The ranges within which these numbers varied was different in each column.

I then wrote a VBA procedure that identified the distinct values in each column, and wrote those distinct values underneath the analyzed values.

This procedure used a Collection approach first to identify and then write the distinct values, and then three different Dictionary-based approaches to doing the same.  Each approach was run 20 times in succession.

For all four of these approaches, my procedure captured the start time and end time, and thus the time required for the operation.

The procedure then wrote the results of each trial to a worksheet for comparison and analysis.

To test the impact of early binding vs. late binding, I made two copies of the workbook, using the same test data and the same test code (differing only in how I declared and instantiated my Dictionary variable).

Benchmark-Early-Binding.xlsm
Benchmark-Late-Binding.xlsm

Adding Items to the Dictionary

Function Params Example
Add Key, Item dict.Add «Apples», 50

We can add items to the dictionary using the Add function. Items can also be added by assigning a value which we will look at in the next section.

Let’s look at the Add function first. The Add function has two parameters: Key and Item. Both must be supplied

dict.Add Key:="Orange", Item:=45
dict.Add "Apple", 66
dict.Add "12/12/2015", "John"
dict.Add 1, 45.56

In the first add example above we use the parameter names. You don’t have to do this although it can be helpful when you are starting out.

The Key can be any data type. The Item can be any data type, an object, array, collection or even a dictionary. So you could have a Dictionary of Dictionaries, Array and Collections. But most of the time it will be a value(date, number or text).

If we add a Key that already exists in the Dictionary then we will get the error

The following code will give this error

dict.Add Key:="Orange", Item:=45

' This line gives an error as key exists already
dict.Add Key:="Orange", Item:=75

Other useful functions

Function Parameters Example
Count N/A dict.Count
Remove Key dict.Remove «Apples»
RemoveAll N/A dict.RemoveAll

The three functions in the above table do the following:

  1. Count – returns the number of items in the Dictionary.
  2. Remove – removes a given key from the Dictionary.
  3. RemoveAll – removes all items from the Dictionary

The following sub shows an example of how you would use these functions

' https://excelmacromastery.com/
Sub AddRemoveCount()

    Dim dict As New Scripting.Dictionary

    ' Add some items
    dict.Add "Orange", 55
    dict.Add "Peach", 55
    dict.Add "Plum", 55
    Debug.Print "The number of items is " & dict.Count
    
    ' Remove one item
    dict.Remove "Orange"
    Debug.Print "The number of items is " & dict.Count
    
    ' Remove all items
    dict.RemoveAll
    Debug.Print "The number of items is " & dict.Count

End Sub

Remember that you can download all the code examples from the post. Just go to the at the top.

Методы и свойства словаря

Методы объекта Dictionary

Метод Add
Метод Add добавляет в словарь новую пару ключ–элемент.

1 Dictionary.AddКлюч,Элемент
  • Ключ – обязательный аргумент, представляющий ключ добавляемой пары.
  • Элемент – обязательный аргумент, представляющий элемент добавляемой пары.

Если добавляемый ключ в словаре уже есть, VBA Excel сгенерирует ошибку.

Метод Exists
Метод Exists возвращает логическое значение, указывающее, существует ли в словаре указанный ключ. True – указанный ключ существует, False – указанный ключ не существует.

1 Dictionary.Exists(Ключ)

Метод Items
Метод Items возвращает массив всех элементов в словаре.

1 Dictionary.Items

Метод Keys
Метод Keys возвращает массив всех ключей в словаре.

1 Dictionary.Keys

Метод Remove
Метод Remove удаляет из словаря одну пару ключ–элемент.

1 Dictionary.Remove(Ключ)

Если указанный ключ не существует, произойдет ошибка.

Метод RemoveAll
Метод RemoveAll удаляет из словаря все пары ключей и элементов.

1 Dictionary.RemoveAll

Свойства объекта Dictionary

Свойство CompareMode
Свойство CompareMode задает или возвращает режим сравнения ключей в словаре. Используется для чтения и записи.

1 Dictionary.CompareModeСравнение

Необязательный аргумент Сравнение используется только при записи и может принимать следующие значения:

  • –1 (vbUseCompareOption) – выполняется сравнение, заданное оператором Option Compare.
  • (vbBinaryCompare) – выполняется двоичное сравнение.
  • 1 (vbTextCompare) – выполняется текстовое сравнение.

По умолчанию выполняется двоичное сравнение, при котором значение свойства CompareMode равно (vbBinaryCompare).

Свойство Count
Свойство Count возвращает количество элементов в словаре. Только для чтения.

1 Dictionary.Count

Свойство Item
Свойство Item задает или возвращает элемент для указанного ключа в словаре. Используется для чтения и записи.

1 Dictionary.Item(Ключ)=Элемент
  • Ключ – обязательный аргумент, представляющий из себя существующий ключ для чтения или изменения существующего элемента, или новый ключ для создания новой пары ключ–элемент.
  • Элемент – необязательный аргумент, представляющий выражение, которое заменяет существующий элемент с существующим ключом или добавляет новый элемент в пару ключ–элемент с новым ключом.

Если при создании с новым ключом новой пары ключ–элемент аргумент Элемент не указан, значение элемента будет пустым (Empty).

Свойство Key
Свойство Key задает новое значение ключа для существующего ключа в словаре.

1 Dictionary.Key(Ключ)=НовыйКлюч
  • Ключ – обязательный аргумент, представляющий из себя существующий ключ.
  • НовыйКлюч – обязательный аргумент, представляющий выражение, значение которого заменяет существующий ключ.

Если указанный ключ отсутствует в словаре, произойдет ошибка.

Sorting the Dictionary

Sometimes you may wish to sort the Dictionary either by key or by value.

The Dictionary doesn’t have a sort function so you have to create your own. I have written two sort functions – one for sorting by key and one for sorting by value.

Sorting by keys

To sort the dictionary by the key you can use the SortDictionaryByKey function below

' https://excelmacromastery.com/
Public Function SortDictionaryByKey(dict As Object _
                  , Optional sortorder As XlSortOrder = xlAscending) As Object
    
    Dim arrList As Object
    Set arrList = CreateObject("System.Collections.ArrayList")
    
    ' Put keys in an ArrayList
    Dim key As Variant, coll As New Collection
    For Each key In dict
        arrList.Add key
    Next key
    
    ' Sort the keys
    arrList.Sort
    
    ' For descending order, reverse
    If sortorder = xlDescending Then
        arrList.Reverse
    End If
    
    ' Create new dictionary
    Dim dictNew As Object
    Set dictNew = CreateObject("Scripting.Dictionary")
    
    ' Read through the sorted keys and add to new dictionary
    For Each key In arrList
        dictNew.Add key, dict(key)
    Next key
    
    ' Clean up
    Set arrList = Nothing
    Set dict = Nothing
    
    ' Return the new dictionary
    Set SortDictionaryByKey = dictNew
        
End Function

The code below, shows you how to use SortDictionaryByKey

' https://excelmacromastery.com/
Sub TestSortByKey()

    Dim dict As Object
    Set dict = CreateObject("Scripting.Dictionary")
    
    dict.Add "Plum", 99
    dict.Add "Apple", 987
    dict.Add "Pear", 234
    dict.Add "Banana", 560
    dict.Add "Orange", 34
    
    PrintDictionary "Original", dict
    
    ' Sort Ascending
    Set dict = SortDictionaryByKey(dict)
    PrintDictionary "Key Ascending", dict
    
    ' Sort Descending
    Set dict = SortDictionaryByKey(dict, xlDescending)
    PrintDictionary "Key Descending", dict
    
End Sub

Public Sub PrintDictionary(ByVal sText As String, dict As Object)
    
    Debug.Print vbCrLf & sText & vbCrLf & String(Len(sText), "=")
    
    Dim key As Variant
    For Each key In dict.keys
        Debug.Print key, dict(key)
    Next
End Sub

Sorting by values

To sort the dictionary by the values you can use the SortDictionaryByValue function below.

' https://excelmacromastery.com/
Public Function SortDictionaryByValue(dict As Object _
                    , Optional sortorder As XlSortOrder = xlAscending) As Object
    
    On Error GoTo eh
    
    Dim arrayList As Object
    Set arrayList = CreateObject("System.Collections.ArrayList")
    
    Dim dictTemp As Object
    Set dictTemp = CreateObject("Scripting.Dictionary")
   
    ' Put values in ArrayList and sort
    ' Store values in tempDict with their keys as a collection
    Dim key As Variant, value As Variant, coll As Collection
    For Each key In dict
    
        value = dict(key)
        
        ' if the value doesn't exist in dict then add
        If dictTemp.exists(value) = False Then
            ' create collection to hold keys
            ' - needed for duplicate values
            Set coll = New Collection
            dictTemp.Add value, coll
            
            ' Add the value
            arrayList.Add value
            
        End If
        
        ' Add the current key to the collection
        dictTemp(value).Add key
    
    Next key
    
    ' Sort the value
    arrayList.Sort
    
    ' Reverse if descending
    If sortorder = xlDescending Then
        arrayList.Reverse
    End If
    
    dict.RemoveAll
    
    ' Read through the ArrayList and add the values and corresponding
    ' keys from the dictTemp
    Dim item As Variant
    For Each value In arrayList
        Set coll = dictTemp(value)
        For Each item In coll
            dict.Add item, value
        Next item
    Next value
    
    Set arrayList = Nothing
    
    ' Return the new dictionary
    Set SortDictionaryByValue = dict
        
Done:
    Exit Function
eh:
    If Err.Number = 450 Then
        Err.Raise vbObjectError + 100, "SortDictionaryByValue" _
                , "Cannot sort the dictionary if the value is an object"
    End If
End Function

The code below shows you how to use SortDictionaryByValue

' https://excelmacromastery.com/
Sub TestSortByValue()

    Dim dict As Object
    Set dict = CreateObject("Scripting.Dictionary")
    
    dict.Add "Plum", 99
    dict.Add "Apple", 987
    dict.Add "Pear", 234
    dict.Add "Banana", 560
    dict.Add "Orange", 34
    
    PrintDictionary "Original", dict
    
    ' Sort Ascending
    Set dict = SortDictionaryByValue(dict)
    PrintDictionary "Value Ascending", dict
    
    ' Sort Descending
    Set dict = SortDictionaryByValue(dict, xlDescending)
    PrintDictionary "Value Descending", dict
    
End Sub

Public Sub PrintDictionary(ByVal sText As String, dict As Object)
    
    Debug.Print vbCrLf & sText & vbCrLf & String(Len(sText), "=")
    
    Dim key As Variant
    For Each key In dict.keys
        Debug.Print key, dict(key)
    Next key
    
End Sub

The Key and Case Sensitivity

Some of the string functions in VBA have a . This is used for functions that compare strings. It is used to determine if the case of the letters matter.

BigStockPhoto.com

The Dictionary uses a similar method. The CompareMode property of the Dictionary is used to determine if the case of the key matters. The settings are

vbTextCompare: Upper and lower case are considered the same.

vbBinaryCompare: Upper and lower case are considered different. This is the default.

With the Dictionary we can use these settings to determine if the case of the key matters.

' https://excelmacromastery.com/
Sub CaseMatters()
    
    Dim dict As New Scripting.Dictionary
    dict.CompareMode = vbBinaryCompare
    dict.Add "Orange", 1
    
    ' Prints False because it considers Orange and ORANGE different 
    Debug.Print dict.Exists("ORANGE")    
    
    Set dict = Nothing

End Sub

This time we use vbTextCompare which means that the case does not matter

' https://excelmacromastery.com/
Sub CaseMattersNot()
    
    Dim dict As New Scripting.Dictionary
    dict.CompareMode = vbTextCompare
    dict.Add "Orange", 1
    
    ' Prints true because it considers Orange and ORANGE the same
    Debug.Print dict.Exists("ORANGE")    
    
    Set dict = Nothing

End Sub

Note: The Dictionary must be empty when you use the CompareMode property or you will get the error: “Invalid procedure call or argument”.

Things to Watch Out For

vbBinaryCompare (the case matters) is the default and this can lead to subtle errors. For example, imagine you have the following data in cells A1 to B2.

Orange, 5
orange, 12

The following code will create two keys – on for “Orange” and one for “orange”. This is subtle as the only difference is the case of the first letter.

' https://excelmacromastery.com/
Sub DiffCase()

    Dim dict As New Scripting.Dictionary
    
    dict.Add Key:=(Range("A1")), Item:=Range("B1")
    dict.Add Key:=(Range("A2")), Item:=Range("B2")

End Sub

If you do use vbTextCompare for the same data you will get an error when you try to add the second key as it considers “Orange” and “orange” the same.

' https://excelmacromastery.com/
Sub UseTextcompare()

    Dim dict As New Scripting.Dictionary
    dict.CompareMode = vbTextCompare
    
    dict.Add Key:=(Range("A1")), Item:=Range("B1")
    ' This line will give an error as your are trying to add the same key
    dict.Add Key:=(Range("A2")), Item:=Range("B2")

End Sub

If you use the assign method then it does not take the CompareMode into account. So the following code will still add two keys even though the CompareMode is set to vbTextCompare.

' https://excelmacromastery.com/
Sub Assign()
    
    Dim dict As New Scripting.Dictionary
    dict.CompareMode = vbTextCompare
    
    ' Adds two keys
    dict(Range("A1")) = Range("B1")
    dict(Range("A2")) = Range("B2")
    
    ' Prints 2
    Debug.Print dict.Count
    
End Sub

Available Options

  1. VB6/VBA Collection object. Provided with the programming language this class allows adding items with or without key and retrieving them by key or by index. Keys can only be of datatype and key matching is always case-insensitive. Collection supports iteration over its items with the syntax and is optimized for fast adding and retrieving items by key. It can be created like this: . This object does not exist in VBScript.
  2. Scripting.Dictionary comes with the «Microsoft Scripting Runtime» COM library (scrrun.dll) and is a very fast key-value-store that supports iteration over its items with the syntax. Every item must have a key. Keys can be of any datatype or they can be objects. Number keys are data-type-insensitive which means that the key as = as = as . String key matching can be either case-sensitive or case-insensitive. If the library is not referenced in your project (F2 > Right Mouse Click > References), you have to use the syntax to create a new Dictionary object. Scripting.Dictionary does not exist on Apple Mac environments.
  3. DictCollection is implemented in VB6/VBA and supports adding items with or without keys. It has two emulation modes and that mimic the behavior of Dictionary and Collection. Iteration using the syntax is not supported (not possible with VB6/VBA classes). Keys have to be Strings and key matching can be case-sensitive or case-insensitive. Items and keys are stored internally as arrays so retrieving items by index is very fast. DictCollection has extended functionality like , , , , , , , , , and , and comes with useful String and Array functions like , , and . Keys can also be searched and filtered using wildcards like using the , functions.

A Quick Guide to the VBA Dictionary

Function Example
Early binding reference “Microsoft Scripting Runtime”
(Add using Tools->References from the VB menu)
Declare (early binding) Dim dict As Scripting.Dictionary
Create(early binding) Set dict = New Scripting.Dictionary
Declare (late binding) Dim dict As Object
Create(late binding) Set dict = CreateObject(«Scripting.Dictionary»)
Add item (key must not already exist) dict.Add Key, Value e.g. dict.Add «Apples», 50
Change value at key. Automatically adds if the key does not exist. dict(Key) = Value e.g. dict(«Oranges») = 60
Get a value from the dictionary using the key Value = dict(Key) e.g. appleCount = dict(«Apples»)
Check if key exists dict.Exists(Key)e.g. If dict.Exists(«Apples») Then
Remove item dict.Remove Key e.g. dict.Remove «Apples»
Remove all items dict.RemoveAll
Go through all items (for each loop) Dim key As Variant For Each key In dict.Keys     Debug.Print key, dict(key)Next key
Go through all items (for loop — early binding only) Dim i As Long For i = 0 To dict.Count — 1    Debug.Print dict.Keys(i),      dict.Items(i)Next i
Go through all items (for loop — early and late binding) Dim i As LongFor i = 0 To dict.Count — 1Debug.Print dict.Keys()(i), dict.Items()(i)Next i
Get the number of items dict.Count
Make key case sensitive (the dictionary must be empty). dict.CompareMode = vbBinaryCompare
Make key non case sensitive (the dictionary must be empty). dict.CompareMode = vbTextCompare
Рейтинг
( Пока оценок нет )
Editor
Editor/ автор статьи

Давно интересуюсь темой. Мне нравится писать о том, в чём разбираюсь.

Понравилась статья? Поделиться с друзьями:
Работатека
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: