Содержание
- Ассоциативные массивы
- Excel vba ассоциативные массивы
- Что такое массив
- Типы массивов
- Объявление массивов
- Объявление фиксированных массивов
- Объявление динамических массивов
- ReDim [Preserve] varname(subscripts) [As Type]
- Изменение элементов массива
- Чтение элементов массива
- Определение границ массива
- LBound( array [, dimension])
- UBound( array [, dimension])
- Перебор элементов массива
- Передача массивов в подпрограммы
- Массив с элементами типа массив
- Функция Array
- Array( arglist )
- Функция Split
- Split(expression[, delimiter[, limit[, compare]]])
- Нюансы работы с динамическими массивами
- Неинициализированный массив
- Расширение массива
- Удаление массива
- Получение массива на основе диапазона Excel
- Дополнительные источники
Ассоциативные массивы
Ассоциативный массив (или словарь) — это набор элементов, доступ к которым осуществляется не по индексу (как это было в обычном массиве), а по ключу. В качестве ключа можно указать число или строку. Элементы словаря могут содержать данные произвольного типа. Чтобы получить элемент, необходимо указать ключ, который использовался при сохранении значения.
В языке VBA нет встроенной поддержки ассоциативных массивов, однако можно получить доступ к ним через объект Scripting.Dictionary . Создать объект позволяет следующий код:
Объект Scripting.Dictionary поддерживает следующие методы:
- Add() — добавляет новый элемент по ключу. В качестве ключа можно использовать число или строку. Если элемент с указанным ключом уже существует, то возникнет ошибка и программа будет остановлена. Формат метода:
- Item( ) — возвращает элемент по указанному ключу. Можно как получить значение по ключу, так и присвоить значение. Если элемент с указанным ключом отсутствует, то он будет добавлен. Пример:
- Exists( ) — возвращает значение True , если элемент с указанным ключом существует, и False — в противном случае;
- Remove( ) — удаляет элемент с указанным ключом. Если ключ не существует, то возникнет ошибка и программа будет остановлена. Пример:
- RemoveAll() — удаляет все элементы:
- Keys() — возвращает массив со всеми ключами;
- Items() — возвращает массив со всеми значениями. Пример:
- Key() — позволяет изменить название ключа. Имеет следующий формат:
Объект Scripting.Dictionary содержит следующие свойства:
- Count — количество элементов:
- CompareMode — задает режим сравнения ключей. Обратите внимание на то, что установка нового значения возможна только для пустого объекта. Можно указать следующие значения:
- vbBinaryCompare — 0 — двоичное сравнение (значение по умолчанию). Сравнение зависит от регистра символов;
- vbTextCompare — 1 — текстовое сравнение. Регистр символов при сравнении не учитывается.
Пример использования текстового сравнения:
Перебрать все элементы ассоциативного массива можно с помощью цикла For Each. Next . В качестве примера выведем элементы словаря двумя способами. Первый способ использует метод Key() , возвращающий массив с ключами ассоциативного массива. Во втором случае мы просто указываем ассоциативный массив в качестве параметра. На каждой итерации цикла будет возвращаться ключ, с помощью которого внутри цикла можно получить значение, соответствующее этому ключу (листинг 2.7).
Листинг 2.7. Перебор элементов ассоциативного массива
Статьи по Visual Basic for Applications (VBA)
Помощь сайту
ПАО Сбербанк:
Счет: 40817810855006152256
Реквизиты банка:
Наименование: СЕВЕРО-ЗАПАДНЫЙ БАНК ПАО СБЕРБАНК
Корреспондентский счет: 30101810500000000653
БИК: 044030653
КПП: 784243001
ОКПО: 09171401
ОКОНХ: 96130
Скриншот реквизитов
Источник
Excel vba ассоциативные массивы
Массивы очень упрощают процесс программирования. Без них практически невозможно написать универсальную программу. Например, представьте себе, что вы манипулируете информацией о квартирах жилого дома. Вы объявляете переменные K1 — для первой квартиры, K2 — для второй и так далее. K1=54 будет означать, что площадь первой квартиры 54 кв.м., К2=72 и т.д. Теперь представим, что нам надо подсчитать общую площадь всех квартир в доме. Очевидно, что это что-то типа Total_S = K1+K2+. +Kn. В одном доме у нас 36 квартир, а в другом 144. Представляете бредовость процесса подобного программирования? Если в первом случае я должен буду использовать 36 отдельных переменных для вычисления общей площади, то для второго дома уже 144. Очень быстро вы придёте к мысли, что вам нужна переменная, состоящая из нумерованных ячеек. Тогда обретают смысл все те операторы циклов, входящие в состав любого языка программирования. Но об этом чуть позже.
Что такое массив
Массив — переменная, состоящая из некоторого количества однотипных элементов. У массива, как и у любой другой переменной, есть имя. А доступ к конкретному элементу массива осуществляется через указание в скобках после имени его индекса. Например, A(5) означает, что я обращаюсь к элементу с индексом 5 массива, имеющего имя A.
Типы массивов
Массивы в VBA и во многих других языках программирования делятся на 2 класса:
Фиксированные массивы . Такие массивы состоят из заранее известного количества элементов. Это количество определяется во время объявления массива и уже не может быть изменено в процессе его жизненного цикла. Вы, конечно же, сможете использовать меньшее количество элементов, но не существует способа увеличить количество элементов сверх объявленного.
Динамические массивы . Эти массивы можно «переобъявлять» в процессе жизненного цикла. То есть мы можем управлять количеством элементов динамических масивов в зависимости от наших потребностей. Это очень удобно, так как в подавляющем большинстве случаев программист не может заранее знать, с каким объёмом данных он столкнётся. Если вы собираетесь писать более-менее универсальные программы, то этот тип массивов определенно стоит изучить.
Объявление массивов
Объявление фиксированных массивов
Рекомендация : при объявлении массивов VBA я советую вам давать всем именам префикс » arr «. Я сторонник венгерской нотации.
Как мы видим, тут объявлено 2 одномерных массива arrTemp и arrTest . Одномерные массивы в программировании также часто называют векторами . Типом элементов первого массива является Long , второго массива — String . В этом типе синтаксиса в скобках указан максимальный индекс ( верхняя граница ) элемента массива. А что насчёт минимального индекса ( нижней границы ) массива? По-умолчанию минимальным индексом является ноль. В данном случае стандартное поведение интерпретатора языка VBA можно изменить при помощи оператора option base <0|1>. Option base 1 заставляет VBA считать нижней границей массива — единицу, а не ноль.
Таким образом, по-умолчанию массив arrTemp имеет 11 элементов — от 0 до 10. Но, если в начало модуля, в котором этот массив объявляется, вставить оператор Option Base 1 , то массив arrTemp будет иметь 10 элементов — от 1 до 10.
Помимо вышеуказанного вы вправе использовать следующий синтаксис, который НЕ зависит от option base <0|1>:
таким образом вы в явном виде указываете и нижнюю, и верхнюю границы. Как видите, нижняя граница совершенно не обязательно должна начинаться с 0 или 1. Более того, индексы границ могут принимать и отрицательные значения, главное чтобы нижняя была меньше верхней.
Помимо одномерных массивов, можно объявлять и массивы с размерностью больше единицы.
arrMulti — двумерный массив, а arrData3 — трёхмерный. Первый содержит 11*31=341 элемент, второй — 2*3*10=60 элементов. Теоретически допускается объявлять до 60 размерностей массива.
Какие типы данных могут стать элементами массива? Тут всё, как в шутке про фамилию еврея, — абсолютно любой тип данных годится на роль элемента массива, включая объектные типы, User Data Type , другие массивы (через тип Variant ). Если вы не указываете при объявлении тип данных массива, то предполагается, что этим типом является тип Variant .
Объявление динамических массивов
Динамические массивы объявляться так:
Однако, использовать их после такого объявления пока ещё нельзя. Необходимо выделить память под массив. Особенность работы с динамическим массивом как раз состоит в том, что программист отвечает за его своевременное расширение (усечение) в памяти. Для этого существует специальный оператор, который имеет следующий синтаксис:
ReDim [Preserve] varname(subscripts) [As Type]
После этого оператора, вы можете использовать элементы массива arrOpen с 0-го по 5-й. Всё, что мы говорили про оператор option base и нижнюю границу, верно и для динамических массивов. Предположим, что вы сохранили информацию в элементах 0-5 и у вас поспела новая порция информации для элементов 6-11. Чтобы разместить в данном массиве новые элементы и не потерять старые, вы должны сделать следующее:
то есть мы тут увеличиваем верхнюю границу массива и используем ключевое слово Preserve , чтобы во время этой операции не потерять текущее содержимое arrOpen , так как в противном случае (без слова Preserve ) массив будет расширен, а память заполнена нулями. Вы также вправе вообще не декларировать массив оператором Dim , а сделать это впервые через ReDim и там же указать лип элементов. Но, если вы в первом ReDim (или Dim ) указали определенный тип элементов, то в последующих операторах ReDim этот тип переопределён быть не может — возникнет ошибка на этапе компиляции проекта.
Изменение элементов массива
Пора бы нам уже научиться пользоваться нашими массивами — то есть записывать информацию в их элементы и считывать её оттуда. Это довольно просто:
Как и с обычными переменными запись информации в элемент массива происходит через оператор присваивания (=), но указанием индекса элемента массива.
Чтение элементов массива
Определение границ массива
В подпрограммах часто приходится иметь дело с массивами, которые переданы вам в качестве параметра (как это сделать показано ниже), поэтому в этом случае очень актуален вопрос определения нижней и верхней границ индекса массива. Для этого в языке предусмотрены 2 функции: LBound и UBound . Первая возвращает нижнюю границу индекса, вторая — верхнюю.
LBound( array [, dimension])
UBound( array [, dimension])
Для одномерных массивов параметр dimension можно не указывать. Для многомерных массивов его указывать необходимо. Кстати, это означает, что, если вы точно не знаете, с каким массивом имеете дело, но необходимо узнать его первую размерность, то лучше использовать вариант UBound(arrTemp,1) , а не UBound(arrTemp) , так как последний вариант вызовет ошибку, если массив окажется многомерным.
Если вы ошибётесь с указанием правильного индекса массива, то возникнет ошибка периода исполнения с кодом 9. Эта же ошибка возникнет, если вы в функции LBound / UBound укажете несуществующую размерность массива (например, 3 для двумерного массива).
Перебор элементов массива
Собственно массивы нужны для того, чтобы хранить в них однотипную информацию и перебирать их в цикле. Как правило, алгоритм делает что-то полезное с одним элементом массива, а цикл повторяет эти типовые действия для всех элементов массива.
Наиболее удобный оператор цикла для перебора элементов массива — это безусловно For . Next .
так же есть способ не заботиться об определении нижней и верхней границ, если алгоритм не требует от нас знания текущего индекса массива:
Вы, конечно, можете перебирать массив и в других типах циклов Do . Loop , но, право, смысла и удобства в этом не много. По крайней мере я не сталкивался, кажется, с ситуациями, когда для перебора массива цикл For не подошёл.
Передача массивов в подпрограммы
Массивы удобнее всего передавать в подпрограммы в виде параметра типа Variant .
Обратите внимание, что функции GetResult в качестве параметра передаются массивы. При чём, в первом случае это массив с типом элементов Long , а во втором — String . За счёт того, что внутри функции используются переменные типа Variant , то сначала функция нам возвращает сумму элементов массива arrIntegers , а во втором результат сложения (конкатенации) строк массива arrStrings . Кроме того, параметр parArray не описан как массив ( parArray As Variant ), но мы внутри функции GetResult ведём себя с ним, как с массивом ( For Each Element In parArray )! Это возможно, так как переменные типа Variant умеют определять, что им присваивается и вести себя далее в соответствии с тем, что они содержат. Если переменной parArray присвоили массив (через вызов функции — строки 17 и 18), то она себя будет вести как массив.
Массив с элементами типа массив
Продемонстрируем, как можно хранить в качестве элементов массива другие массивы.
Результат отладочной печати:
Функция Array
Данная функция полезна для создания справочных массивов. Она возвращает переменную типа Variant , содержащую одномерный массив с типом элементов Variant .
Array( arglist )
Вызов функции без параметров приведёт к возврату массива нулевой длинны. При этом будет наблюдаться интересный эффект LBound вернёт вам 0, а UBound вернёт -1, то есть верхняя граница окажется меньше нижней границы.
Функция Split
Split возвращает одномерный массив, содержащий подстроки, из строкового выражении с учётом указанного разделителя
Split(expression[, delimiter[, limit[, compare]]])
expression — строковое выражение, содержащая подстроки и разделители. Обязательный параметр.
delimiter — текстовый разделитель. Необязательный параметр. Если опущен, то предполагается, что разделителем является символ пробела.
limit — количество подстрок, которое необходимо вернуть. -1 или отсутствие параметра означает, что вернуть надо все подстроки.
compare — константа, указывающая тип сравнения для символов разделителей. 1 — текстовое сравнение (без учёта регистра), 0 — бинарное сравнение (с учётом регистра).
Результат выглядит так:
Если вы в качестве разделителя укажете пустую строку, то на выходе получите массив, состоящий из одного элемента. Кстати, split всегда возвращает массив с нулевой нижней границей вне всякой зависимости от наличия option base 1 .
Нюансы работы с динамическими массивами
Неинициализированный массив
У динамического массива есть такое промежуточное состояние, когда он уже объявлен, но ещё не содержит никаких элементов.
То есть у переменной динамического массива есть такое состояние, когда мы не можем воспользоваться вспомогательными функциями LBound / UBound для определения его (массива) статуса. Это особенно надо учитывать, когда вы пишите подпрограммы, работающие с массивами. Прежде чем работать (перебирать) массив необходимо убедиться, что он проинициализирован, в противном случае программа вылетит с ошибкой 9.
Для этого я предлагаю пользоваться функцией подобной нижеописанной IsNotEmptyArray :
Расширение массива
Как правило, расширять динамический массив приходится в цикле. Возможны 2 стратегии: расширение на 1 элемент, как только в этом есть необходимость (назовём это эластичным расширением), и расширение авансом, когда вы увеличиваете верхнюю границу скачками, скажем сразу на 100 элементов. Реализовав оба варианта, я для себя сделал вывод, что авансовое расширение получилось и компактнее, и работает быстрее, так как операция расширения, вообще говоря, затратна и, чем реже вызывается, тем лучше.
Авансовый метод вышел даже компактнее
Удаление массива
Существует оператор Erase , который полностью освобождает память из-под динамического массива. Будучи вызванным для статического массива он его обнуляет, а если он строковый, то элементам присваивается пустые строки.
Получение массива на основе диапазона Excel
Самый эффективный по скорости способ получить содержимое диапазона Excel для манипулирования в VBA — это скопировать его в массив с элементами Variant. Делается так:
Даже, если вы передаёте в массив столбец или строку, получаемый массив всегда будет иметь 2 измерения. Измерение 1 отвечает за строки, измерение 2 — за столбцы. То есть ячейка C5 будет в элементе arrTemp(5,3) . Нижняя граница таких массивов всегда будет начинаться с единицы.
Дополнительные источники
В качестве источника дополнительной информации по массивам я могу порекомендовать замечательный, исчерпывающий ресурс Чарльза Пирсона (Charles H. Pearson). Его сайт следует штудировать всем, кто серьёзно осваивает VBA. Конкретно по массивам там огромное количество готовых подпрограмм для работы с ними, исходные коды, снабженные подробнейшими комментариями, продвинутые объяснения для копающих в глубину. Без преувеличения великолепный ресурс!
Источник
Друзья, на VBA писать только начинаю, пока не разобрался, но голову ломаю несколько дней
Мне необходимо производить манипуляции с таблицей данных, где в строках некий набор записей, а в столбцах различные подзаписи (типа «продавец», «продукт», «цена»…), то есть налицо двумерный массив. Очень хочется обращаться с этим массивом, как с ассоциативным.
Примерно так
Код |
---|
a[1][key1] = "abc" a[1][key2] = 2 .... a[N][keyM] = "smth" |
Таким образом к элементам массива удобно обращаться по ключам, складывать, вычитать и тд.
Наверняка в VBA есть решение, но для меня оно не открывается.
Что я изучил:
Collection, scripting.dictionary — не подходит, поскольку на один ключ там только одно значение (если я всё правильно понял)
Пользовательский тип данных — не подходит, поскольку набор колонок может меняться от раза к разу (как задавать тип данных динамически, я не нашёл)
Что приходит на ум:
1. Создать двумерный массив (просто скопировать таблицу в массив) вместе с названиями столбцов. Таким образом в первой строке массива у меня будет массив с названиями полей. И когда мне нужно будет обратиться к элементу a[345876][«Продукт»], то я буду искать в первой строке порядковый номер элемента с названием «Продукт»
примерно так
Код |
---|
for i=1 to a[1].Count if aa == "Продукт" then exit For Next' a[345876][i] ' будет искомым элементом двумерного массива |
2. Создать коллекцию массивов и проделать примерно тоже самое. Чем это лучше или хуже не знаю, то мне кажется, это будет одно и тоже.
3. Создать отдельную коллекцию типа ColNames(value=’номер элемента’ key=’имя колонки’). Тогда к элементу a[345876][«Продукт»] можно будет обратиться таким образом a[345876][ColNames.Item(«Продукт» ]
Но всё это, как мне кажется, кривые способы. Наверняка, есть красивое решение для такого рода задач.
Как вообще выглядит моя задача, точнее её часть:
1.У меня есть таблица, которую я копирую из другого файла. Строк в таблице десятки тысяч, столбцов — десятки (количество столбцов и их названия могут меняться от раза к разу)
Таблица примерно такого вида
Клиент | продукт | объём | штуки | Сумма |
k003382 | pr098782 | 45 | -20 | 94.4 |
k084982 | pr98376 | 98 | 87 | -9837 |
k084982 | pr98346 | -34 | 874.2 | -35 |
k084876 | pr98346 | 45 | 7636 | 7736 |
2.Я формирую из комбинации значений нескольких столбцов столбец с ключом, который пока не является уникальным.
3. сортирую таблицу по пока не уникальному ключу
4. формирую массив уникальных ключей (убираю дубликаты)
5. далее нужно суммировать числовые столбцы по уникальному ключу. (это вполне решается прописыванием формул типа суммесли(…), но считает такой объем данных очень долго.
6. дальше нужно будет производить другие операции, но это уже другая история.
Возможно есть способ одновременного формирования списка уникальных ключей и суммирования строк с повторными ключами. Пока я формирую список ключей просто через .RemoveDuplicates
И так финальные вопросы:
1. Как создать такую структуру данных (массив, коллекцию… что угодно), чтобы можно было обращаться по принципу двухмерного ассоциативного массива.
2. Как удалить дубликаты в столбце «A» одновременным суммированием числовых значений в столбцах «D», «E»… там где значения столбца А повторяются.
Буду признателен за любые советы и конструктивную критику
Здесь мы идем… просто скопируем код в модуль, он готов использовать
Private Type hashtable
key As Variant
value As Variant
End Type
Private GetErrMsg As String
Private Function CreateHashTable(htable() As hashtable) As Boolean
GetErrMsg = ""
On Error GoTo CreateErr
ReDim htable(0)
CreateHashTable = True
Exit Function
CreateErr:
CreateHashTable = False
GetErrMsg = Err.Description
End Function
Private Function AddValue(htable() As hashtable, key As Variant, value As Variant) As Long
GetErrMsg = ""
On Error GoTo AddErr
Dim idx As Long
idx = UBound(htable) + 1
Dim htVal As hashtable
htVal.key = key
htVal.value = value
Dim i As Long
For i = 1 To UBound(htable)
If htable(i).key = key Then Err.Raise 9999, , "Key [" & CStr(key) & "] is not unique"
Next i
ReDim Preserve htable(idx)
htable(idx) = htVal
AddValue = idx
Exit Function
AddErr:
AddValue = 0
GetErrMsg = Err.Description
End Function
Private Function RemoveValue(htable() As hashtable, key As Variant) As Boolean
GetErrMsg = ""
On Error GoTo RemoveErr
Dim i As Long, idx As Long
Dim htTemp() As hashtable
idx = 0
For i = 1 To UBound(htable)
If htable(i).key <> key And IsEmpty(htable(i).key) = False Then
ReDim Preserve htTemp(idx)
AddValue htTemp, htable(i).key, htable(i).value
idx = idx + 1
End If
Next i
If UBound(htable) = UBound(htTemp) Then Err.Raise 9998, , "Key [" & CStr(key) & "] not found"
htable = htTemp
RemoveValue = True
Exit Function
RemoveErr:
RemoveValue = False
GetErrMsg = Err.Description
End Function
Private Function GetValue(htable() As hashtable, key As Variant) As Variant
GetErrMsg = ""
On Error GoTo GetValueErr
Dim found As Boolean
found = False
For i = 1 To UBound(htable)
If htable(i).key = key And IsEmpty(htable(i).key) = False Then
GetValue = htable(i).value
Exit Function
End If
Next i
Err.Raise 9997, , "Key [" & CStr(key) & "] not found"
Exit Function
GetValueErr:
GetValue = ""
GetErrMsg = Err.Description
End Function
Private Function GetValueCount(htable() As hashtable) As Long
GetErrMsg = ""
On Error GoTo GetValueCountErr
GetValueCount = UBound(htable)
Exit Function
GetValueCountErr:
GetValueCount = 0
GetErrMsg = Err.Description
End Function
Для использования в приложении VB (A):
Public Sub Test()
Dim hashtbl() As hashtable
Debug.Print "Create Hashtable: " & CreateHashTable(hashtbl)
Debug.Print ""
Debug.Print "ID Test Add V1: " & AddValue(hashtbl, "Hallo_0", "Testwert 0")
Debug.Print "ID Test Add V2: " & AddValue(hashtbl, "Hallo_0", "Testwert 0")
Debug.Print "ID Test 1 Add V1: " & AddValue(hashtbl, "Hallo.1", "Testwert 1")
Debug.Print "ID Test 2 Add V1: " & AddValue(hashtbl, "Hallo-2", "Testwert 2")
Debug.Print "ID Test 3 Add V1: " & AddValue(hashtbl, "Hallo 3", "Testwert 3")
Debug.Print ""
Debug.Print "Test 1 Removed V1: " & RemoveValue(hashtbl, "Hallo_1")
Debug.Print "Test 1 Removed V2: " & RemoveValue(hashtbl, "Hallo_1")
Debug.Print "Test 2 Removed V1: " & RemoveValue(hashtbl, "Hallo-2")
Debug.Print ""
Debug.Print "Value Test 3: " & CStr(GetValue(hashtbl, "Hallo 3"))
Debug.Print "Value Test 1: " & CStr(GetValue(hashtbl, "Hallo_1"))
Debug.Print ""
Debug.Print "Hashtable Content:"
For i = 1 To UBound(hashtbl)
Debug.Print CStr(i) & ": " & CStr(hashtbl(i).key) & " - " & CStr(hashtbl(i).value)
Next i
Debug.Print ""
Debug.Print "Count: " & CStr(GetValueCount(hashtbl))
End Sub