Используйте для каждого оператора, чтобы пройти двумерный массив - для строки / не для столбца - PullRequest
0 голосов
/ 12 ноября 2018

Прежде всего, я не хочу использовать вложенные циклы For, потому что я прочитал, что они станут медленнее с большим объемом данных (n может быть до 10k).Итак, теперь у меня есть массив arrData(n,2), в то время как n является переменной, и я хочу просмотреть этот массив в строке, используя оператор for each.Так вот мой код.Для упрощения я вставил массив (2,2):

Sub test()

    Dim arrData(2, 2) As Variant

    arrData(0, 0) = 0
    arrData(0, 1) = 0
    arrData(0, 2) = 0
    arrData(1, 0) = 1
    arrData(1, 1) = 1
    arrData(1, 2) = 1
    arrData(2, 0) = 2
    arrData(2, 1) = 2
    arrData(2, 2) = 2

    For Each Element In arrData
        MsgBox Element
    Next Element

End Sub

Я получаю 012012012, но хочу получить 000111222.

Ответы [ 2 ]

0 голосов
/ 12 ноября 2018

Во-первых, давайте обратимся к этому заблуждению:

Я читал, что они станут медленнее с большим объемом данных (n может быть до 10 КБ)

Это просто неправда. Многомерные массивы только «медленнее», чем одномерные массивы, потому что адрес памяти индексатора должен быть вычислен (подробнее об этом позже). Скорее всего, вы имеете в виду вычислительную сложность вложенного цикла - число итераций увеличивается как произведение границ каждого цикла. У вас есть фиксированное количество элементов, поэтому оно будет одинаковым независимо от того, как к ним обращаются Если вы хотите выполнить операцию o для каждого члена двумерного массива, вы будете выполнять это вычисление b1 * b2 раз. Период.


Теперь, чтобы объяснить результат, который дает ваш пример кода, давайте посмотрим, как VBA размещает массив в памяти. Я немного упросту это, лишь посмотрев на область данных (есть также структура SAFEARRAY , которая содержит метаинформацию о массиве, но это не совсем уместно). Массив с одним измерением располагается как непрерывная область памяти, и VBA поддерживает указатель на первый элемент. Например, одномерный массив Long будет выглядеть так (Dim foo(4) As Long):

one dimensional array

Структура SAFEARRAY содержит указатель на «Элемент 0», и когда вы обращаетесь к нему в своем коде, он умножает индексатор на длину типа элемента в байтах, а затем возвращает значение по этому адресу памяти. Таким образом, если бы первый элемент был по адресу памяти 0x0000 и вы получили доступ к foo(2), он умножил бы 2 на 4 (длина Long, прибавил бы это к 0x0000 и дал бы вам 4 байта, начинающиеся с 0x0008.

По сути, A + (L * E1), где A - базовый адрес, L - длина элемента, а E1 - запрашиваемый элемент.


Второе измерение добавляет N копий этого макета в память, где N - количество элементов во втором измерении. Итак, массив в вашем примере кода выглядит следующим образом (Dim foo(2, 2) As Long):

two dimensional array

VBA индексирует это так же, как одномерный массив, за исключением второго измерения, оно добавляет произведение индексатора для второго измерения и общую длину полного первого измерения к вычислению адреса для элемента в измерении один ,

В основном, A + (L * E1) + (L * B1 * E2), где B1 - это число элементов первого измерения, а E2 - индекс для второго. Итак, если бы вы обращались к foo(1, 1) с базового адреса 0x0000, это было бы 0 + (4 * 1) + (4 * 3 * 1), или 0x0010.

В сторону - вот почему вы не можете Redim Preserve ничего, кроме верхнего измерения массива - это случай только , когда это простое выделение памяти и копирование.


Итак, поворачиваясь к вашему примеру, ваши значения сохраняются в памяти следующим образом:

OP's array in memory

Когда вы используете For Each, итератор массива VBA просто возвращает вам каждый элемент в порядке следования памяти , так что вы получаете 012012012. Для вашего конкретного примера вы можете получить их обратно в порядке 000111222, перенеся его - то, что вы называете «строкой», на самом деле является первым измерением в вашем примере:

 Sub Example()
    Dim arrData(2, 2) As Variant

    arrData(0, 0) = 0
    arrData(1, 0) = 0
    arrData(2, 0) = 0
    arrData(0, 1) = 1
    arrData(1, 1) = 1
    arrData(2, 1) = 1
    arrData(0, 2) = 2
    arrData(1, 2) = 2
    arrData(2, 2) = 2

    For Each Element In arrData
        Debug.Print Element
    Next Element
End Sub

Это раскладывает массив как в памяти:

transposed two dimensional array


Тем не менее, цикл For Each требует больше затрат, чем простой цикл For, потому что VBA должен использовать перечислитель массива и помещать вызовы _NewEnum в стек. Хотя при индексировании может наблюдаться незначительное увеличение производительности, поскольку оно только добавляет смещение к адресу памяти вместо того, чтобы каждый раз выполнять более длительные вычисления, это более чем перевешивает многократное нажатие и выталкивание стека вызовов. Итак, короче говоря, просто вложите петли:

Dim outer As Long
Dim inner As Long
For outer = LBound(arrData, 1) To UBound(arrData, 1)
    For inner = LBound(arrData, 2) To UBound(arrData, 2)
        Debug.Print arrData(outer, inner)
    Next
Next

В вашем случае вы бы "транспонировали" массив, меняя местами внутренние и внешние циклы.

ПРИМЕЧАНИЕ: Я не использую «строку» в контексте Excel (хотя это был бы первый ранг), а под «транспонированием» я не имею в виду использование функции Transpose в Excel - это будет иметь худшую производительность, чем альтернатива или .

0 голосов
/ 12 ноября 2018

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

Вам необходимо зациклить строки и столбцы для эффективного доступа ко всем элементам, т. Е. Структуре вложенных циклов. Я не знаю более эффективного способа доступа к каждому элементу из двумерного массива.

Где вы можете улучшить свой код, это использовать цикл For, который быстрее, чем For Each, при работе с массивами.

...