MyTetra Share
Делитесь знаниями!
Исчерпывающее описание объекта Dictionary
Время создания: 16.03.2019 23:43
Текстовые метки: Dictionary
Раздел: Разные закладки - VBA - Dictionary-Collection
Запись: xintrea/mytetra_db_adgaver_new/master/base/1512384749n9o8s77l02/text.html на raw.githubusercontent.com

Исчерпывающее описание объекта Dictionary

http://perfect-excel.ru/publ/excel/makrosy_i_programmy_vba/ischerpyvajushhee_opisanie_obekta_dictionary/7-1-0-101

 

Содержание:

1. Что такое Dictionary?

2. Создание Dictionary

3. Свойства и методы объекта Dictionary

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

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

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

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

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

5. Удаление элементов

    5.1. Удаление конкретного элемента

    5.2. Очистка всего словаря

6. Ключи

    6.1. Последовательность хранения

    6.2. Добавление элементов с ключами разных типов

    6.3. Уникальность строковых ключей

    6.4. Генерация уникальных ключей

7. Элементы

    7.1. Типы элементов

    7.2. UDT

8. Доступ к элементам словаря

    8.1. Извлечение элемента по ключу

    8.2. Извлечение элемента по номеру его индекса

    8.3. Извлечение ключа по номеру его индекса

9. Перебор словаря

    9.1. For each по массивам Keys и Items

    9.2. For по массивам Keys и Items

    9.3. Фильтрация элементов

    9.4. Выгрузка словаря в диапазон ячеек

    9.5. Операции с ключами/элементами при помощи формул рабочего листа

10. Файл примера

11. Заключение

12. Использованный источник

1. ЧТО ТАКОЕ DICTIONARY?

Если вы программируете на VBA/VBS, то рано или поздно вынуждены будете познакомиться с объектом Dictionary. Если в двух словах, то Dictionary - это продвинутый массив . Как вы знаете, массив - это упорядоченный набор неких (обычно однородных) элементов. Вот типичный массив:

Элементы пронумерованы и доступны по номеру индекса. Индекс всегда числовой.

 

А вот, что из себя представляет Dictionary (словарь):

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

С другой стороны нечто подобное можно же сделать, используя массив. Давайте объявим двумерный массив:

?

 Dim arrMoney(1 to 2, 1 to 10)

 

  arrMoney(1,1) = "Иван"

  arrMoney(2,1) = 705

  arrMoney(1,2) = "Денис"

  arrMoney(2,2) = 553

  ' и так далее...

Должно быть у словаря есть какие-то преимущества перед таким использованием массивов? И это действительно так!

Давайте пока просто перечислим важнейшие преимущества:

  1. Словарь контролирует уникальность ключей. Два одинаковых ключа не могут быть добавлены в словарь. Это важное свойство, так как программисту очень часто требуется обеспечить или проконтролировать уникальность каких-либо наборов значений, и в этом может с успехом быть использован Dictionary;
  2. Словарь очень эффективно (при помощи встроенного алгоритма бинарного поиска) осуществляет извлечение элементов по известному ключу. В десятки раз быстрее, чем обычный перебор;
  3. У словаря есть встроенный метод (Exists), при помощи которого можно понять, добавлен ли некий ключ в коллекцию;
  4. Словарь позволяет добавлять новые элементы и удалять любые элементы, что, работая с массивами, сделать гораздо сложнее;
  5. Словарь может вернуть все ключи и все элементы в виде отдельных одномерных массивов.

вверх

2. СОЗДАНИЕ DICTIONARY

Существует несколько способов создать объект типа Dictionary. Ознакомимся с ними:


Sub Creation()

   

  ' ВАРИАНТ 1 - раннее связывание

  ' ------------------------------------------------------

  ' декларируем объектную переменную

  Dim dicTemp1 As Dictionary ' нужно, если есть опция Option Explicit

  ' либо можно так: Dim dicTemp1

  ' либо так:       Dim dicTemp1 as Object

   

  ' создаём объект и присваиваем ссылку на него переменной

  Set dicTemp1 = New Dictionary

   

  ' проверяем что объект работает, выводя количество элементов

  ' сейчас их там, конечно же, нет ни одного

  MsgBox dicTemp1.Count

   

   

  ' ВАРИАНТ 2 - раннее связывание

  ' ------------------------------------------------------

  ' декларируем и сразу создаём объект

  Dim dicTemp2 As New Dictionary

   

  ' проверяем что объект работает

  MsgBox dicTemp2.Count

   

   

  ' ВАРИАНТ 3 - раннее связывание (без переменной)

  ' ------------------------------------------------------

  With New Dictionary

    MsgBox .Count

  End With

   

   

  ' ВАРИАНТ 4 - позднее связывание

  ' ------------------------------------------------------

  Dim dicTemp4 ' нужно, если есть опция Option Explicit

  Set dicTemp4 = CreateObject("Scripting.Dictionary")

  MsgBox dicTemp4.Count

 

  

  ' ВАРИАНТ 5 - позднее связывание (без переменной)

  ' ------------------------------------------------------

  With CreateObject("Scripting.Dictionary")

    MsgBox .Count

  End With

     

End Sub


Считается, что методы, использующие позднее связывание надёжнее в плане обеспечения работоспособности программы на разных компьютерах, так как не зависят от настроек Tools - References... редактора VBA.

Однако, учитывая, что библиотека Microsoft Scripting Runtime присутствует везде, начиная с Windows 2000, я думаю, что вы без какого-либо ущерба можете использовать методы раннего связывания. Раннее связывание хорошо тем, что оно несколько быстрее работает, а также во время разработки вы можете пользоваться функцией завершения кода (когда среда программирования вам подсказывает имеющиеся у объекта свойства и методы). Выбор за вами.

вверх

3. СВОЙСТВА И МЕТОДЫ ОБЪЕКТА DICTIONARY

Тип

Идентификатор

Описание

Свойство

Count

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

Свойство

Item

dicObject.Item(key)[ = newitem]
Устанавливает или возвращает элемент с указанным ключом. Чтение/запись.

Свойство

Key

dicObject.Key(key) = newkey
Заменяет ключ элемента на новое значение.

Свойство

CompareMode

dicObject.CompareMode[ = compare]
Устанавливает и возвращает режим сравнения текстовых ключей в словаре. Чтение/запись.

Метод

Add

dicObject.Add (key, item)
Добавляет пару ключ-элемент в словарь.

Метод

Exists

dicObject.Exists(key) 
Возвращает true, если указанный ключ существует в словаре, либо false - в противном случае.

Метод

Items

dicObject.Items( ) 
Возвращает массив, состоящий из всех элементов, имеющихся в коллекции.

Метод

Keys

dicObject.Keys( ) 
Возвращает массив, состоящий из всех ключей, имеющихся в коллекции.

Метод

Remove

dicObject.Remove(key)
Удаляет из словаря элемент с указанным ключом.

Метод

RemoveAll

dicObject.RemoveAll( ) 
Полностью очищает словарь от элементов. Сам объект словаря при этом не уничтожается.

вверх

4. НАПОЛНЕНИЕ СЛОВАРЯ

 

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

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

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

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

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

вверх

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

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

?

Sub HowToAddElement()

  

  Dim dicCountry 'Объявляем переменную для словаря

  Dim row As Integer

  Dim key As String, item As Double ' переменные для ключа и элемента словаря

  Set dicCountry = CreateObject("Scripting.Dictionary") ' создаём словарь

   

  With Sheets("Example").Range("SquareByCountry") ' работаем с именованным диапазоном

    For row = 1 To .Rows.Count ' перебираем строки ИД

      key = CStr(.Cells(row, 1).Value) ' получаем ключ из первого столбца текущей строки ИД

      item = CDbl(.Cells(row, 2).Value) ' получаем элемент из второго столбца текущей строки ИД

      dicCountry.Add key, item ' и добавляем новый элемент в коллекцию

    Next

  End With

 

End Sub




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

вверх

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

?

 ' другой вариант для 12-й строки предыдущего примера кода

  dicCountry.Item(key) = item ' и добавляем новый элемент в коллекцию

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

вверх

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

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


Sub ImplicitKeyAddition()

  

  Dim dicCountry

  Dim row As Integer, key As String

  Dim varTemp ' объявляем любую переменную

  Set dicCountry = CreateObject("Scripting.Dictionary") ' создаём словарь

   

  With Sheets("Example").Range("SquareByCountry")

    For row = 1 To .Rows.Count

      key = CStr(.Cells(row, 1).Value)

      varTemp = dicCountry.Item(key) ' добавляем КЛЮЧ в словарь (элемент будет пустой)

    Next

  End With

 

End Sub


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

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

?

  key = CStr(.Cells(row, 1).Value)

' значение Item(key) считывается в varTemp только в случае, если словарь содержит этот key,

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

  if dicCountry.Exists(key) then varTemp = dicCountry.Item(key)




5. УДАЛЕНИЕ ЭЛЕМЕНТОВ

Есть 2 варианта удаления элементов из словаря:

5.1. Удаление конкретного элемента

?

  ' удаляем элемент с ключом "США" из словаря

  dicCountry.Remove "США"


5.2. Очистка всего словаря

?

 ' очистка словаря от всех элементов

  dicCountry.RemoveAll


Полагаю, комментировать тут нечего.


6. КЛЮЧИ

6.1. Последовательность хранения

Следует понимать, что элементы в словаре хранятся в той последовательности, в которой они добавлялись в словарь. Менять эту последовательность можно только путём полной перестройки словаря (хотя не совсем понятно для чего это может понадобиться).

вверх

6.2. Добавление элементов с ключами разных типов

Продемонстрируем, добавление элементов с ключами разных типов в словарь:

?

Sub ShowDifferentKeys()

 

  On Error Resume Next

 

  Dim dicTemp As New Dictionary

  Dim strShow As String, key As Variant

 

  dicTemp.Add Date, "Date type" ' добавляем ключ с типом дата

   

  dicTemp.Add True, "Boolean type" ' добавляем ключ с логическим типом

   

  dicTemp.Add CDbl(12.4567), "Double type" ' добавляем ключ с типом Double

   

  dicTemp.Add CInt(12999), "Integer type" ' добавляем ключ с типом Integer

   

  dicTemp.Add "Red", "String type" ' добавляем строковый ключ

   

  dicTemp.Add ActiveSheet, "Worksheet object" ' добавляем ключ в виже Worksheet объекта

 

  For Each key In dicTemp.Keys ' перебираем ключи словаря (тут немного забегаю вперёд)

    Err.Clear ' очищаю ошибку

    ' Для каждого элемента словаря формирую строку ключ - тип ключа

    ' Тип ключа выясняем при помощи функции TypeName

    strShow = strShow & CStr(key) & vbTab & vbTab & TypeName(key) & vbCr

    ' Поскольку у нас один из ключей имеет объектный тип, то CStr(key) выдаст ошибку

    ' которую мы перехватим и CStr(key) заменим на key.Name

    If Err Then strShow = strShow & key.Name & vbTab & vbTab & TypeName(key) & vbCr

  Next

   

  ' Выведем на экран результат

  MsgBox strShow, vbInformation, "Он реально хранит типы ключей!"

 

End Sub

Вот, что мы получим, выполнив представленный код:


6.3. Уникальность строковых ключей

При помощи свойства CompareMode можно управлять тем, как Dictionary будет реагировать на одинаковые текстовые ключи, набранные в разном регистре. При значении CompareMode равным константе TextCompare (1) разный регистр игнорируется и ключи считаются идентичными, а при константе BinaryCompare (0) такие ключи считаются разными. Менять CompareMode можно только, когда словарь пуст (либо только создан, либо только что очищен).

?

Sub ShowCompareMode()

  On Error Resume Next

   

  With New Dictionary

    .CompareMode = TextCompare ' текстовый режим - игнорирует регистр

     

    .Add "Россия", 17098

    .Add "РоссиЯ", 17098 ' тут будет сгенерировано исключение

     

    MsgBox Join(.Keys, vbLf), , "Россия и РоссиЯ"

    .RemoveAll 'очищаем словарь, иначе изменение CompareMode вызовет ошибку

     

     

    .CompareMode = BinaryCompare ' двоичный режим - различает регистр

     

    .Add "США", 9519

    .Add "Сша", 9519 ' будут добавлены оба элемента

     

    MsgBox Join(.Keys, vbLf), , "США и Сша"

     

  End With

End Sub

вверх

6.4. Генерация уникальных ключей

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

?

Sub ShowUniqKeys()

  Dim Element

  With CreateObject("Scripting.Dictionary")

    For Each Element In Array("Один", "Два", "Три", "Четыре", "Пять")

      ' Используем Count в качестве значения ключа

      ' таким образом ключи будут 0,1,2,3,4

      .item(.Count) = Element

    Next

    MsgBox Join(.Keys, vbLf)

  End With

End Sub

вверх

7. ЭЛЕМЕНТЫ

7.1. Типы элементов

Продемонстрируем добавление в словарь элементов разных типов:

?

Sub ShowDifferentItems()

 

  Dim dicTemp As New Dictionary

  Dim strShow As String, Item As Variant

 

  With dicTemp

    .Add .Count, Date ' добавляем элемент с типом дата

     

    .Add .Count, True ' добавляем элемент с логическим типом

     

    .Add .Count, CDbl(12.4567) ' добавляем элемент с типом Double

     

    .Add .Count, CInt(12999) ' добавляем элемент с типом Integer

     

    .Add .Count, "Red" ' добавляем строковый элемент

     

    .Add .Count, ActiveSheet ' добавляем элемент в виде Worksheet объекта

     

    .Add .Count, Array(11, 22, 33) ' добавляем элемент типа массив

   

    For Each Item In .Items ' перебираем элементы словаря (тут немного забегаю вперёд)

      Err.Clear ' очищаю ошибку

      ' Для каждого элемента словаря формирую строку c типом элемента

      ' Тип элемента выясняем при помощи функции TypeName

      strShow = strShow & TypeName(Item) & vbCr

    Next

  End With

     

  ' Выведем на экран результат

  MsgBox strShow, vbInformation, "Он реально хранит типы элементов!"

 

End Sub

вверх

7.2. UDT

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

?

Public Red As Byte

Public Green As Byte

Public Blue As Byte


далее становится возможным следующее:

?

Sub UDT_through_Class()

   

  Dim objRGB As MyRGB ' объявляем переменную с типом нашего класса

   

  Set objRGB = New MyRGB ' создаём наш объект

   

  ' Присваиваем какие-то значения

  objRGB.Red = 100

  objRGB.Green = 150

  objRGB.Blue = 200

   

  With New Dictionary ' создаём словарь

    .Add "test", objRGB ' добавляем в качестве элемента наш объект

    ' Теперь мы можем обращаться к отдельным свойствам данного элемента словаря

    ' в частности, мы тут прочли красную ("Red") компоненту структуры

    MsgBox "К примеру, красная компонента равна " & .Item("test").Red

  End With

   

End Sub

вверх

8. ДОСТУП К ЭЛЕМЕНТАМ СЛОВАРЯ

8.1. Извлечение элемента по ключу

Этот способ мы уже обсуждали, но для полноты картины повторимся.

?

Sub Get_Item_By_Key()

  With New Dictionary

     

    .Add "Россия", 17098 ' добавляем элемент

     

    MsgBox .Item("Россия") ' и извлекаем элемент

     

  End With

End Sub

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

вверх

8.2. Извлечение элемента по номеру его индекса

Для извлечения конкретного элемента по его индексу необходимо использовать конструкцию Items()(i), где i - индекс элемента, начинающийся с нуля. Это довольно неожиданный синтаксис, я не припомню, чтобы он применялся где-то ещё кроме Dictionary. Согласно таблице, приведенной выше, Items - свойство, содержащее одномерный массив всех элементов словаря. Также есть соответствующий массив Keys для всех ключей словаря.

?

Sub Get_Item_By_Index()

   

  Dim dicTemp As New Dictionary ' объявляем и сразу создаём словарь

  Dim varCountry As Variant ' вспомогательная переменная

   

  With dicTemp ' работаем со словарём

   

    ' Организуем цикл по массиву из 5 элементов

    For Each varCountry In Array("Russia", "Canada", "China", "USA", "Brazil")

      ' наполняем словарь парами вида "Key 1" - "Russia", "Key 2" - "Canada"

      .Item("Key " & CStr(.Count + 1)) = varCountry ' используем способ из 4.3.

    Next

     

    ' Извлекаем элементы по их индексу! Обратите внимание на синтаксис!

    ' наиболее неожиданно тут то, что после Items надо использовать пустые скобки

    MsgBox .Items()(0) ' вернёт Russia, так как нумерация начинается с нуля

    MsgBox .Items()(3) ' вернёт USA

     

    MsgBox .Items()(.Count - 1) ' классика - последний элемент!

    MsgBox .Items()(UBound(.Keys)) ' немного экзотики - тоже последний элемент :)

     

  End With

End Sub

вверх

8.3. Извлечение ключа по номеру его индекса

Безусловно то же самое справедливо и для извлечения ключей.

?

MsgBox .Keys()(0) ' вернёт "Key 1" для предыдущего примера

 

MsgBox .Keys()(.Count - 1) ' вернёт "Key 5"

вверх

9. ПЕРЕБОР СЛОВАРЯ

9.1. For each по массивам Keys и Items

?

Sub Loop_1()

   

  Dim dicTemp As New Dictionary

  Dim varCountry As Variant

  Dim varKey As Variant, varItem As Variant

  Dim strShow As String

   

  With dicTemp

     

    For Each varCountry In Array("Russia", "Canada", "China", "USA", "Brazil")

      .Item("Key " & CStr(.Count + 1)) = varCountry

    Next

     

    For Each varKey In .Keys ' организуем цикл по элементам  масива Keys

      strShow = strShow & .Item(varKey) & vbLf

    Next

     

    MsgBox strShow, vbInformation, "Цикл по Keys"

     

    strShow = vbNullString

     

    For Each varItem In .Items ' организуем цикл по элементам  масива Items

      strShow = strShow & varItem & vbLf

    Next

     

    MsgBox strShow, vbInformation, "Цикл по Items"

     

  End With

End Sub

вверх

9.2. For по массивам Keys и Items

?

Sub Loop_2()

   

  Dim dicTemp As New Dictionary

  Dim varCountry As Variant

  Dim strShow As String, i As Integer

   

  With dicTemp

     

    For Each varCountry In Array("Russia", "Canada", "China", "USA", "Brazil")

      .Item("Key " & CStr(.Count + 1)) = varCountry

    Next

     

    For i = 0 To .Count - 1

      strShow = strShow & .Items()(i) & vbLf

    Next

     

    MsgBox strShow, vbInformation, "Цикл по Items"

     

    strShow = vbNullString

     

    For i = 0 To .Count - 1

      strShow = strShow & .Keys()(i) & vbLf

    Next

     

    MsgBox strShow, vbInformation, "Цикл по Keys"

     

  End With

End Sub

вверх

9.3. Фильтрация элементов

Для фильтрации словаря по ключам или элементам крайне удобно использовать VBA функцию Filter. Вот как это может выглядеть:

?

Sub Filter_1()

   

  Dim dicTemp As New Dictionary

  Dim varCountry As Variant

  Dim varItem As Variant

   

  With dicTemp

     

    For Each varCountry In Array("Russia", "Canada", "China", "USA", "Brazil")

      .Item("Key " & CStr(.Count + 1)) = varCountry

    Next

     

    ' Используем VBA функцию Filter!

    For Each varItem In Filter(.Items, "r", , vbTextCompare)

      MsgBox varItem ' будет отфильтрованы Russia и Brazil

    Next

     

  End With

End Sub

вверх

9.4. Выгрузка словаря в диапазон ячеек

?

Sub Fill_Range()

   

  Dim dicTemp As New Dictionary

  Dim varCountry As Variant

  Dim varItem As Variant

   

  With dicTemp

     

    For Each varCountry In Array("Russia", "Canada", "China", "USA", "Brazil")

      .Item("Key " & CStr(.Count + 1)) = varCountry

    Next

     

    ' вытягиваем элементы по строке

    Cells(1, 1).Resize(, .Count) = .Keys

    Cells(2, 1).Resize(, .Count) = .Items

 

    ' вытягиваем элементы по столбцу

    Cells(4, 1).Resize(.Count) = Application.Transpose(.Keys)

    Cells(4, 2).Resize(.Count) = Application.Transpose(.Items)

 

  End With

End Sub

Результат:

вверх

9.5. Операции с ключами/элементами при помощи формул рабочего листа

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

?

Sub Operations_with_Keys()

   

  Dim dicTemp As New Dictionary

  Dim arrItems, arrKeys

  Dim i As Integer

   

  With dicTemp

     

    ' определяем массивы для ключей и элементов

    arrKeys = Array(17098, 9976, 9598, 9519, 8511)

    arrItems = Array("Russia", "Canada", "China", "USA", "Brazil")

         

    ' добавляем массивы в словарь

    For i = 0 To UBound(arrKeys)

      .Add arrKeys(i), arrItems(i)

    Next

             

    ' находим максимальное значение ключа, а по ключу извлекаем элемент (название страны)

    MsgBox .Item(Application.Max(.Keys)) & " has the largest territory."

 

  End With

End Sub

К вашим услугам и другие функции, такие как: Min(), Large(), Small() и т.д.

вверх

10. ФАЙЛ ПРИМЕРА

Скачать примеры кода VBA

вверх

11. ЗАКЛЮЧЕНИЕ

Я надеюсь, что данная статья помогла вам хорошенько разобрать с объектом Dictionary. В моих ближайших планах рассказать, как при помощи словаря строить произвольные иерархические структуры. Так же стоит упомянуть, что в VBA есть такой встроенный объект Collection. Однако, по своим функциональным возможностям он достаточно уныл и вчистую проигрывает Dictionary, поэтому я не хочу тратить на него силы и время. Единственное его преимущество это то, что он часть MS Office, а словарь - часть MS Windows, поэтому первый работает в MS Excel for Mac, а второй - нет. Но пока (да и вообще) в нашей стране это обстоятельство можно с лёгкостью игнорировать.

вверх

12. ИСПОЛЬЗОВАННЫЙ ИСТОЧНИК

Массу конкретного материала по объекту Dictionary я подчерпнул из этой замечательной (огромной!) статьи  некоего, по всей видимости испанского, автора, имени которого на сайте нет. Очень рекомендую. На сайте также даётся огромное количество примеров работы с объектной моделью Outlook!

 

Читайте также:

  • Массивы в VBA
  • Структуры данных и их эффективность
  • Работа с объектом Range
  • Работа с объектом Range (часть 2)

·                                                                                                                                 

·         1

·         2

·         3

·         4

·         5

Категория: Макросы и программы VBA  | Добавил: dsb75  (20.07.2015) | Автор: Батьянов Денис E  W

Просмотров: 2225 | Комментарии: 47 | Рейтинг: 5.0/3

 

Всего комментариев: 47

Порядок вывода комментариев:       По умолчанию Сначала новые Сначала старые

0

4 6 Aleksey    (19.11.2015 14:22)

Для сортировки элементов по текстовым ключам функция есть?

 

0

4 7 dsb75    (19.11.2015 15:24)

Словарь не отсортируешь, можно взять массив Keys и отсортировать через функции сортировки массивов. А если надо иметь сортированный словарь, то только через пересоздание.

 

0

1 1 Kokonoko    (05.09.2015 21:23)

Еще возник технический вопрос реализации работы словарей: 
Самая часто встречающаяся рабочая задача собрать неким образом обработанные данные из разных источников в двумерную таблицу – матрицу NxM. Где N количество 
строк мы никогда не знает, а M- количество столбцов, почти всегда известно. И далее эту матрицу просто вставить в определённое место. 
А так как поячеичная вставка данных занимает много ресурсовсистемы и выполняется крайне медленно. Пришлось исхитрятся в оптимизации). В конце концов я вывел для себя способ: 
- сами данные собираю(1 x M) собираю в одномерный массив (1 x M), а массивы собираю в коллекцию. 

Когда приходит время выгружать на лист иду циклом по коллекцииа массивы сразу гружу в Range строки, и получается нечто похожее: 
For i = 1 To collRows.Count 
ws.Range(ws.Cells(i, 1), ws.Cells(i, UBound(collRows(i)))) = collRows(i) 
Next i 

Когда прочитал Вашу статью решил взять на вооружение новыйметод: 
Т.е. основные данные также собирать в массивы (1 x M), а массивы собирать всловарь. 
И на выходе при помощи .Items  -> мы получимквадратную матрицу которую можно будет одномоментно (без цикла) грузить на лист 
в Range. Но не тут то 
было… 
Матрица получается какая-то не понятная экселю и на лист невыгружается ничего: т.е. 
Range(“”)=dct.items() 
будет пустым. Для того чтобы данные выгрузились, нужно использоватьдвойное транспонирование: 
Range(“”)=Application.Transpose(Application.Transpose(dct.Items)) 
– и это работает! Но только до тех пор, пока не натыкаетсяна ограничение «Transpose», 
65 тыщ. строк в транспонируемом массиве – дальше Дамп.

 

0

1 3 dsb75    (05.09.2015 23:21)

Я обычно использую такой метод: 
если Arr - двумерный массив, который я хочу видеть на листе, то вставляется он так: 
ActiveCell.Resize(UBound(Arr, 1), UBound(Arr, 2)) = Arr 

Надо заметить, что это красивый код, он мне очень нравится.

 

0

1 5 Kokonoko    (07.09.2015 13:30)

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

 

0

1 7 dsb75    (07.09.2015 15:46)

Покажите мне полный код. Я что-нибудь посоветую.

 

0

1 9 Kokonoko    (07.09.2015 16:54)

Пока не надо) 
После перезагрузки экселя ошибка ушла...

 

0

1 8 dsb75    (07.09.2015 16:03)

Не совсем понял, раз вы говорите про 1xM, то значит данные вы разбиваете по строкам, а столбцы видимо будут идти непрерывно в целевом диапазоне. Почему бы тогда не подготовить в памяти массив NxM и не вставить его сразу целиком? Ведь, когда вы формируете свою коллекцию, то на тот момент N наверняка ведь уже известна?

 

0

2 0 Kokonoko    (07.09.2015 17:05)

Потому что он динамический, с неизвестным количеством строк. 
N - мы узнаём в самом конце консолидации данных. 
https://fotki.yandex.ru/next/users/kokonoko/album/496947/view/1326452

 

0

2 1 dsb75    (07.09.2015 18:34)

Короче, ясно. Словарь с с массивами 1xM можно оставить. После заполнения словаря его содержимое перепишите в массив NxM (N вы уже знаете на этот момент dicObj.Count). И никакое транспонирование вам не потребуется. 

Альтернативный вариант можно базировать на том, чтобы работать с динамическими массивами M x N, а не (N x M). Поскольку внешнюю размерность массива можно расширять через ReDim, то словарь вам не потребуется. А подпрограмму для транспонирования массива вы можете взять у 
Чарльза Пирсона

 

0

2 2 Kokonoko    (10.09.2015 11:27)

Вариант с промежуточным массивом тоже не прошёл тестирование: т.к. словарь занимает большое количество памяти, и при количестве строк от 80-100 тыс. excel периодически падает в дамп "Out of Memory", еще при попытке средимить промежуточный массив по параметрам словаря. 
А вариант через с Redim обратного массива,  для меня не очень красивый из-за перевернутости массива и не даст суммарного прироста производительности, т.к. redim preserve ест много ресурсов (а редимить скачками усложняет процесс реализации). 

Просто хотел взять в работу более совершенный и простой инструмент 

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

Спасибо за советы!

 

0

2 3 dsb75    (10.09.2015 13:12)

Сомневаюсь, что построчная выгрузка на лист не является узким местом в плане производительности. Сколько это занимает по времени для 50 000 строк?

 

0

2 4 dsb75    (10.09.2015 13:13)

Кстати в плане идей посмотрите ещё эту  статью

 

0

2 6 Kokonoko    (14.09.2015 13:35)

Дома позанимался НИОКР ом (правда там машина несколько быстрее чем на работе). 
Дано: 41 файл откуда будет извлекаться 73606 обработанных строк с данными. 

Время чистой консолидации (мин:сек) 
                                           в словарь  1-43 
                                           в коллекцию 1-41 
Время полной работы программы (консолидация+выгрузка в файл) в строке вида: 
 ws.Range(ws.Range("A" & lngR), ws.Cells(lngR, UBound(dctRows.items()(0)))) = dctRows(lngR - 1) ' Т.е. с извлечением размера массива на лету из Item'а 
                                            словарь 1-46 
                                            коллекция 3-23 

Время полной работы программы (консолидация+выгрузка в файл) в строке вида: 
ws.Range(ws.Range("A" & lngR), ws.Cells(lngR, 8)) = dctRows(lngR - 1) 
' Т.е. без извлечения размера массива из Item'а: 
                                            словарь 1-46 
                                            коллекция 2-36 

Из чего можно сделать вывод, что на мощной машине (I7-4770s +16GB RAM+SSD) построчная выгрузка данных словаря почти не влияет на время работы общей процедуры, причём не зависимо от отдельной распаковки элемента для получения ширине массива. 
Коллекция выгружает данные медленнее, а если ещё и отдельно распаковывать элементы, то производительность снижается ещё сильнее.

 

0

2 8 dsb75    (14.09.2015 14:04)

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

 

0

2 9 dsb75    (14.09.2015 20:06)

Душевная конфигурация компа  

Я тут смотрел на ваши скрины и не могу взять в толк, накой чОрт вы всё-таки это делаете построчно? 
1. Открыли файл, нашли диапазон N  x M, 
2. прочитали его в динамический массив 
3. динамический массив поместили в словарь 
4. Следующий файл 

5. Консолидация в обратном порядке 

Код рациональней и эффективность будет выше.

 

0

3 0 Kokonoko    (14.09.2015 22:22)

И правда душевная, а главное всё в 3-х литровом корпусе и почти бесшумно , можно и бесшумно сделать (но лениво единственный вентилятор менять на Noctua Noctua NF-S12A ULN)  

Это усложение процесса обработки)) На самом деле нет проблемы обработать какие-то файлы. Я просто в поисках идеально сбалансированного (для себя) метода обработки: самый простой и короткий с точки реализации и отдладки, и при этом быстрый. Такой вот поиск совершенства 
 За советы Спасибо!!! 

Теперь главное с глюками словарей (или моими) разобраться и буду их использовать намного чаще)

 

0

3 2 dsb75    (14.09.2015 23:53)

Где же усложнение? Давайте посоревнуемся 

 

0

3 7 Kokonoko    (15.09.2015 22:47)

Можно))

 

0

3 9 dsb75    (15.09.2015 22:49)

С вас формальное описание задачи 

 

0

4 0 Kokonoko    (15.09.2015 22:55)

Хорошо только я на нём повишу некоторое время. 
Если брать файлы заказчика - то их придётся сначала переработать, ибо политика безопасности...

 

0

4 2 dsb75    (15.09.2015 22:57)

Нагенерите всякий мусор - без разницы

 

0

4 3 Kokonoko    (21.09.2015 13:16)

Добрый день. 
Обещанное ТЗ) 
Нужно сгенерировать csv файл состоящий из 8-ми полей из n-го количества файлов. 
Правила заполнения на листе "Запуск" в книге "Формирование CSV на основе ОИБ.." CSV - результат (Master Data от 21.09.2015) и мой файл делающий обработку (Формирование CSV на основе ОИБ v3.2 Раб) я приложил. 
https://yadi.sk/i/vMLnSRvGjDYg8  
https://yadi.sk/d/56t9QFaejDYgE  
https://yadi.sk/i/jzQiWXEhjDYgG

 

0

4 4 dsb75    (21.09.2015 23:27)

Здравствуйте! 
Ознакомился с вашими данными и алгоритмом. У вас тут есть ПРИЧИНА, по которой вы считываете данные построчно - данные то идут не подряд, а перемежаются скрытыми строками (вы, кажется, об этом не упоминали). 

Стиль, конечно, есть куда совершенствовать, но в главном тут навряд ли что-то можно улучшить - всё проистекает из структуры данных...

 

0

4 5 Kokonoko    (22.09.2015 15:07)

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

Но словари всё равно крутая штука, отдельное СПАСИБО что разъяснили про поведение словаря при добавлении его в watches (проблемы с ненужным добавлением исчезли), теперь в разы больше пользуюсь словарями (вместо коллекций). 

А стиль... всегда есть куда совершенствоваться 

 

0

8  Kokonoko    (05.09.2015 20:54)

На основе Вашей статьи вывел перебор элементов (Items) в цикле аналогичном циклу по коллекции, если использовать в виде ключа "dicTemp.Count + 1". 

Sub Loop_3() 
   
Dim dicTemp As Object 
Dim varCountry As Variant 
Dim strShow As String, i As Integer 
   
   
   Set dicTemp = CreateObject("Scripting.Dictionary") 'Мне так привычнее 
      
   For Each varCountry In Array("Russia", "Canada", "China", "USA", "Brazil") 
     dicTemp(dicTemp.Count + 1) = varCountry 
   Next varCountry 
    
   For i = 1 To dicTemp.Count 
     strShow = strShow & dicTemp(i) & vbLf 
   Next i 
   
   MsgBox strShow 
    
   strShow = vbNullString 
End Sub 

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

 

0

9  dsb75    (05.09.2015 21:16)

C нуля начинается нумерация Items, а не с 1 и заканчивается на Count-1. 

И вот это не имеет смысла "strShow = vbNullString"

 

0

1 0 Kokonoko    (05.09.2015 21:21)

Со вторым согласен, просто оставил. 
А с первым - нет. Мы когда добавляем 5 элементов в словарь, - у нас dicTemp.count будет 5. Я извлекаю по ключу подогнанному под count.

 

0

1 2 dsb75    (05.09.2015 23:16)

Да, я не совсем прав. Items, конечно, начинается с нулевого элемента, только он тут не при чём. Тут речь про ключи, а они могут быть вообще любыми. Тогда, что за проблемы самопроизвольного добавления...?

 

0

1 4 Kokonoko    (07.09.2015 13:25)

Бывает что у меня (и других сотрудников) словари подглючивают. 
Обычно это выражается так: в одной процедуре идёт формирование и заполнения словаря. После выхода из неё, и при входе в другую процедуру (где значения должны выгружаться) в словаре появляются 1 или несколько пустых элементов в конце. Обычно это решается перезапуском экселя или перезагрузкой компьютера. Возможно в словарь попадает какой то мусор из кэша.

 

0

1 6 dsb75    (07.09.2015 15:41)

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

 

0

2 5 Kokonoko    (14.09.2015 13:22)

Добрый день. 
Наловил 2 глюка словарей. Код процедуры неизменен. 
1 Глюк: при инициализации словаря в нём уже есть пустой элемент: 
https://yadi.sk/i/vP0erLjZj5Hxp  
2 Глюк: при обработке exists вместо проверки создаётся пустой элемент с данным ключом. 
https://yadi.sk/i/cwCcXkMCj5JEd  

Бывает ещё 3 глюк, когда пустые элемены добавляются в словарь после окончания работы с ним. Пруфа пока нет)) 

Данные глюки могут возникать, а может и всё нормально отрабатываться. 
Причины возникновения я ещё не выяснил. Решается перезапуском Excel в более тяжёлых случаях, перезагрузкой компа.

 

0

2 7 dsb75    (14.09.2015 13:53)

Полагаю, дело в том, что ваши действия по контролю состояния объекта Dictionary во время отдадки и создают эти элементы. По крайней мере, в глюке № 2. Вы ведь добавили соответствующий watch.

 

0

3 1 Kokonoko    (14.09.2015 22:24)

Очень инетересно замечание, я всегда мониторю переменные через watch (поэтому и with очень редко использую). А почему watch может давать такой эффект и как с эти бороться?

 

0

3 3 dsb75    (15.09.2015 00:01)

Ну,  в статье же написано, что Dictionary при чтении свойства Item создаёт соответствующий Key. Вот вы, видимо, добавили в watch 
dct(strBudg), когда strBudg ещё не было инициализировано и - вуаля - 
создался пустой элемент с пустым ключом. 
Чтобы ничего не создавалось, надо не трепыхать свойство Item (делать отладочную печать 
через Items?), либо писать программы так, чтобы пустые узлы ни на что не 
могли повлиять - фильтровать их...

 

0

3 4 Kokonoko    (15.09.2015 22:31)

Похоже у меня пробел в знаниях) Думал что в watch только приходят параметры (работает исключительно как монитор) и из него ничего не уходит в программу... Тогда всё становится яснее.

 

0

3 5 dsb75    (15.09.2015 22:43)

О, я тоже этого не знал, пока вы не сообщили о своём глюке. Это гипотеза, которая хорошо укладывается в известные факты 

 

0

3 6 Kokonoko    (15.09.2015 22:45)

Да, очень похоже, причём очень сильно. 
А я на что только не грешил, думал нужно ключ создавать чисто текстовый или что словарь может подсосать какой-нибудь мусор из оперативки если программу прервать))

 

0

3 8 dsb75    (15.09.2015 22:48)

Создатели объекта Dictionary большие оригиналы - менять структуры по факту чтения, это очень необычно.... Спорное архитектурное решение, чего уж там.

 

0

4 1 Kokonoko    (15.09.2015 22:57)

Это точно...

 

0

6  Dman    (05.09.2015 11:58)

Проверил. Действительно можно. 

Ещё раз спасибо.

 

0

7  dsb75    (05.09.2015 12:01)

Гишпанец, поди говорил про Keys и Items, а вы так расширительно интерпретировали. Бывает 

 

0

4  Dman    (05.09.2015 11:30)

Случайно наткнулся на Ваш сайт. 
Понравился. Понравилось оформление, понравилось наполнение, понравился стиль изложения. 
Хочу сделать дополнение к статье - стоит указать в явном виде, что нельзя редактировать отдельные элементы массивов, сохранённых в Dictionary, - только полной заменой всего массива как единого целого. Это я, кстати, почерпнул в указанном Вами источнике. 

А в целом - очень хорошая статья по Dictionary. Спасибо.

 

0

5  dsb75    (05.09.2015 11:44)

Спасибо, но я с вами не согласен по поводу массивов. С чего бы это нельзя?

 

0

2  Kokonoko    (03.09.2015 16:20)

Это самая Лучшая статья по VBA которую я читал!!! 
Пользуюсь словарями уже 2 с лишним года и не знал половины возможностей!!! 
Специально зарегистрировался, чтобы сказать Вам СПАСИБО !!!

 

0

3  dsb75    (03.09.2015 20:29)

Приятно, чёрт побери 

 

0

1  dsb75    (01.08.2015 17:29)

Статья то хорошая? нет? 

 

 
MyTetra Share v.0.65
Яндекс индекс цитирования