массивы VBA, варианты и массивы вариантов
Этот ответ обеспечивает основу, необходимую для понимания некоторого кода в других ответах и для понимания, почему я отверг альтернативный подход.
Чтобы объявить простые переменные, я пишу:
Dim A As Long
Dim B As String
Dim C As Boolean
Dim D As Integer
Dim E As Double
В VBA есть набор встроенных типов данных, которые не сильно отличаются от доступных на других языках.
VBA имеет другой тип:
Dim F As Variant
Вариант может рассматриваться как нетипизированный или как контейнер. Если я напишу:
A = 5 ' OK because A is Long
A = "abc" ' Will fail a n alphabetic string cannot be saved in a Long
A = "123" ' OK because string "123" is automatically converted to integer 123
С другой стороны, я могу без ошибок написать следующее:
F = 5
F = "abc"
F = True
F = 1.23
Каждое из этих значений будет храниться правильно. F может использоваться в любом выражении, для которого подходит его текущее значение:
F = 5
F = F + 2
F = "abc"
F = F & "def"
Все приведенные выше утверждения действительны, но
F = "abc"
F = F + 2
потерпит неудачу, потому что после установки F в «abc» его нельзя использовать в арифметическом выражении.
Variant также может содержать рабочий лист Excel, документ Word или любой объект Office. Вариант также может содержать массив. Когда Variant содержит объект или массив, синтаксис такой, как будто Variant стал этим объектом или массивом. Итак:
F = Worksheets("Data”)
F.Range("A1") = "abc"
Выше, F теперь фактически является переменной типа Worksheet, и к F можно получить доступ к любым свойствам или методам Worksheet. Это было просто для краткого ознакомления с полным объемом вариантов; остальная часть этого урока ограничена массивами.
Я могу «преобразовать» вариант в массив одним из двух способов:
1) F = VBA.Array(1, "abc", True)
2) ReDim F(0 To 2)
VBA.Array - это функция, которая возвращает одномерный массив Variant с нижней границей 0 и достаточным количеством элементов для хранения предоставленных значений. Я также могу написать F = Array(1, "abc", True)
. Функция Array
аналогична функции VBA.Array
, за исключением того, что нижняя граница зависит от текущего значения и значения команды Option Base
.
Я использую функцию Array
только если я собираюсь использовать функцию LBound
для определения нижней границы. Я не до конца понимаю, что и на что не влияет команда Option Base
, поскольку она не полностью задокументирована. Я видел различия между разными версиями разных продуктов Microsoft, которые, я уверен, случайны. Я уверен, что новый программист Microsoft предположил, что старый продукт работает разумным образом, когда это не так. Я очень стараюсь указывать как нижнюю, так и верхнюю границы, если могу. Если я не могу указать нижнюю границу, я проверяю это. Я по-прежнему использую подпрограммы, написанные в Excel 2003. Я считаю, что отсутствие проблем, с которыми я сталкиваюсь при работе со старыми подпрограммами, объясняется тем, что я избегаю предположений о том, как работает Excel, если они не полностью задокументированы.
Возвращаясь к учебнику, ReDim F(0 To 2)
эффективно преобразует F в массив из трех элементов.
Все предыдущие обсуждения были об одномерных массивах. Также возможны обычные многомерные массивы:
Dim G(1 to 5) As Long
Dim H(1 to 5, 1 To 4) As String
Dim I(1 to 5, 1 To 4, 0 To 3) As Boolean
или
Dim G() As Long
Dim H() As String
Dim I() As Boolean
ReDim G(1 to 5)
ReDim H(1 to 5, 1 To 4)
ReDim I(1 to 5, 1 To 4, 0 To 3)
В первом блоке количество и размер измерений фиксируются во время компиляции. Со вторым блоком число и размер измерений устанавливаются во время выполнения и могут быть изменены.
В любом случае синтаксис для доступа:
G(n) = 3
H(n, m) = "abc"
I(n, m, o) = True
Этот тип многомерного не подходит для ваших требований. Хотя границы могут быть изменены во время выполнения, количество измерений не может быть изменено в операторе ReDim, оператор Select должен быть выбран из длинного списка предварительно подготовленных операторов ReDim с одним для каждого возможного числа измерений.
Альтернатива - рваные или неровные массивы, хотя они не рваны.
Рассмотрим:
Dim F As Variant
ReDim F(0 To 2)
F(0) = VBA.Array(1, 2, 3)
F(1) = VBA.Array(4, 5, 6)
F(2) = VBA.Array(7, 8, 9)
Я превратил F в массив из трех элементов, а затем превратил каждый элемент F в массив. Чтобы получить доступ к элементам внутренних массивов, я пишу: F (n) (m), где n и m могут быть 0, 1 или 2.
Я могу продолжить:
F (0) (0) = VBA.Array (10, 11, 12)
После этого изменения элемент F (0) (0) (0) имеет значение 10, а F (0) (0) (1) - значение 11.
Я могу продолжать это до бесконечности.Я читал, что VBA имеет ограничение 60 измерений с обычными многомерными массивами.Я не пробовал, но я не понимаю, почему при использовании этого метода, кроме памяти, было бы какое-либо ограничение на число измерений.
Этот метод, по-видимому, имеет то же ограничение, что и обычные многомерные массивы.Я могу написать F (0) (0) или F (0) (0) (0), но я не могу изменить глубину простой переменной во время выполнения.
Существует также проблема, заключающаяся в том, что ReDim F (0)) (От 0 до 2) отклоняется компилятором как неверный синтаксис.Вот почему я использовал VBA.Array для преобразования F (0) в массив.
Решение - рекурсия.Рассмотрим:
Call ReDimVar(F, "1 To 2", "3 To 4", "0 To 5")
ReDimVar может:
ReDim F(1 To 2)
Call ReDimVar(F(1), "3 To 4", "0 To 5")
Call ReDimVar(F(2), "3 To 4", "0 To 5")
Все это может быть выполнено с помощью простых циклов.Я отказался от этого метода, потому что рекурсия медленная, а ваш вопрос подразумевает значительные объемы данных и множество измерений.Однако, чтобы продемонстрировать, что это будет работать, поиграйте со следующим:
Sub TryMDVA()
' Demonstrate how to:
' 1) Convert a Variant into a multi-dimension array
' 2) Store values in every element of that multi-dimension array
' 3) Extract values from every element of that multi-dimension array
Dim Coords() As Long
Dim ElementValue As String
Dim InxB As Long ' Index for both Bounds and Coords
Dim InxD1 As Long
Dim InxD2 As Long
Dim InxD3 As Long
Dim LwrBnds As Variant
Dim MDVA As Variant
Dim UppBnds As Variant
LwrBnds = Array(1, 0, -3)
UppBnds = Array(2, 5, 4)
ReDim Bounds(LBound(LwrBnds) To UBound(LwrBnds))
ReDim Coords(LBound(LwrBnds) To UBound(LwrBnds))
Call FormatMDVA(MDVA, LwrBnds, UppBnds)
Debug.Print "Results of formatting MDVA"
Debug.Print "Bounds of MDVA are " & LBound(MDVA) & " to " & UBound(MDVA)
Debug.Print "Bounds of MDVA(1) are " & LBound(MDVA(1)) & " to " & UBound(MDVA(1))
Debug.Print "Bounds of MDVA(2) are " & LBound(MDVA(2)) & " to " & UBound(MDVA(2))
Debug.Print "Bounds or MDVA(1)(0) are " & LBound(MDVA(1)(0)) & " to " & UBound(MDVA(1)(0))
Debug.Print "Bounds or MDVA(2)(5) are " & LBound(MDVA(2)(5)) & " to " & UBound(MDVA(2)(5))
' Initialise Coords to lower bound of each dimension
For InxB = LBound(LwrBnds) To UBound(LwrBnds)
Coords(InxB) = LwrBnds(InxB)
Next
Do While True
' Build element value from coordinates
ElementValue = Coords(LBound(Coords))
For InxB = LBound(LwrBnds) + 1 To UBound(LwrBnds)
ElementValue = ElementValue & "." & Coords(InxB)
Next
' Store element value in element of MDVA specified by Coords
Call PutElement(MDVA, Coords, ElementValue)
' Step Coords. Think of Coords as a speedometer with each wheel marked
' with the available index values for a dimension. Starting on the right,
' check each wheel against the relevant ubound. If it is less than the
' ubound, step it by 1. If it is the upper bound, reset it to the lower
' bound and try the next wheel to the left. If the leftmost wheel is
' to be reset, Coords has been set to all possible values.
For InxB = UBound(LwrBnds) To LBound(LwrBnds) Step -1
If Coords(InxB) < UppBnds(InxB) Then
Coords(InxB) = Coords(InxB) + 1
Exit For
Else
If InxB = LBound(LwrBnds) Then
Exit Do
End If
Coords(InxB) = LwrBnds(InxB)
End If
Next
Loop
Debug.Print "Example values from within MDVA"
Debug.Print "MDVA(1)(0)(-3) = " & MDVA(1)(0)(-3)
Debug.Print "MDVA(1)(0)(-2) = " & MDVA(1)(0)(-2)
Debug.Print "MDVA(2)(3)(0) = " & MDVA(2)(3)(0)
Debug.Print "MDVA(2)(5)(4) = " & MDVA(2)(5)(4)
' Initialise Coords to upper bound of each dimension
For InxB = LBound(UppBnds) To UBound(UppBnds)
Coords(InxB) = UppBnds(InxB)
Next
Debug.Print "List of all values in MDVA"
Do While True
' Output value of element of MDVA identified by Coords
Debug.Print "MDVA(" & Coords(LBound(UppBnds));
For InxB = LBound(UppBnds) + 1 To UBound(UppBnds)
Debug.Print ", " & Coords(InxB);
Next
Debug.Print ") = """ & GetElement(MDVA, Coords) & """"
' Set next value of Coords. Similar to code block in PutElement
' but in the opposite direction
For InxB = UBound(LwrBnds) To LBound(LwrBnds) Step -1
If Coords(InxB) > LwrBnds(InxB) Then
Coords(InxB) = Coords(InxB) - 1
Exit For
Else
If InxB = LBound(LwrBnds) Then
Exit Do
End If
Coords(InxB) = UppBnds(InxB)
End If
Next
Loop
End Sub
Sub FormatMDVA(ByRef MDVA As Variant, LwrBnds As Variant, UppBnds As Variant)
' Size MDVA according to the bounds in the first elements of LwrBnds and
' UppBnds. If there are further elements in LwrBnds and UppBnds, call
' FormatMDVA to format every element of MDVA according to the remaining
' elements.
Dim InxB As Long
Dim InxM As Long
Dim LB As Long
Dim SubLwrBnds As Variant
Dim SubUppBnds As Variant
LB = LBound(LwrBnds)
ReDim MDVA(LwrBnds(LB) To UppBnds(LB))
If LBound(LwrBnds) = UBound(LwrBnds) Then
' All bounds applied
Else
' Another dimension to format
ReDim SubLwrBnds(LB + 1 To UBound(LwrBnds))
ReDim SubUppBnds(LB + 1 To UBound(UppBnds))
' Copy remaining bounds to new arrays
For InxB = LB + 1 To UBound(LwrBnds)
SubLwrBnds(InxB) = LwrBnds(InxB)
SubUppBnds(InxB) = UppBnds(InxB)
Next
For InxM = LwrBnds(LB) To UppBnds(LB)
Call FormatMDVA(MDVA(InxM), SubLwrBnds, SubUppBnds)
Next
End If
End Sub
Function GetElement(ByRef MDVA As Variant, ByRef Coords() As Long) As Variant
' Return the value of the element of MDVA identified by Coords
Dim InxC As Long
Dim LB As Long
Dim SubCoords() As Long
LB = LBound(Coords)
If LB = UBound(Coords) Then
' Have reached innermost array
GetElement = MDVA(Coords(LB))
Else
' At least one more nested array
ReDim SubCoords(LB + 1 To UBound(Coords))
For InxC = LB + 1 To UBound(Coords)
SubCoords(InxC) = Coords(InxC)
Next
GetElement = GetElement(MDVA(Coords(LB)), SubCoords)
End If
End Function
Sub PutElement(ByRef MDVA As Variant, ByRef Coords() As Long, _
ElementValue As Variant)
' Save the value of ElementValue in the element of MDVA identified by Coords
Dim InxC As Long
Dim LB As Long
Dim SubCoords() As Long
LB = LBound(Coords)
If LB = UBound(Coords) Then
' Have reached innermost array
MDVA(Coords(LB)) = ElementValue
Else
' At least one more nested array
ReDim SubCoords(LB + 1 To UBound(Coords))
For InxC = LB + 1 To UBound(Coords)
SubCoords(InxC) = Coords(InxC)
Next
Call PutElement(MDVA(Coords(LB)), SubCoords, ElementValue)
End If
End Sub