Какой самый эффективный / быстрый способ циклически проходить между строками в VBA (Excel)? - PullRequest
22 голосов
/ 18 ноября 2011

Я знаю, что VBA в Excel - не самая быстрая вещь, но мне нужен самый эффективный (то есть, самый быстрый) способ циклически проходить по большой выборке строк.

В настоящее время у меня есть:

For Each c In Range("$A$2:$A$" & Cells(Rows.count, "A").End(xlUp).row
    ' do stuff
Next c

«Что делать» включает вставку строки здесь и там (поэтому мне нужно сохранить динамический поиск диапазона.)

Есть идеи (глядя на 10 000 строк +)?

EDIT Я уже использую

Application.ScreenUpdating = False
Application.Calculation = xlManual

Ответы [ 3 ]

36 голосов
/ 18 ноября 2011

Если вы просто перебираете 10 тыс. Строк в столбце А, выведите строку в вариантный массив, а затем пройдитесь по ней.

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

Dim i As Long
Dim varray As Variant

varray = Range("A2:A" & Cells(Rows.Count, "A").End(xlUp).Row).Value

For i = 1 To UBound(varray, 1)
    ' do stuff to varray(i, 1)
Next

Вот пример того, как вы можете добавлять строки после оценки каждой ячейки. В этом примере просто вставляется строка после каждой строки, в которой есть слово «foo» в столбце А. Не то, что «+2» добавляется к переменной i во время вставки, поскольку мы начинаем с A2. Было бы +1, если бы мы начинали наш массив с A1.

Sub test()

Dim varray As Variant
Dim i As Long

varray = Range("A2:A10").Value

'must step back or it'll be infinite loop
For i = UBound(varray, 1) To LBound(varray, 1) Step -1
    'do your logic and evaluation here
    If varray(i, 1) = "foo" Then
       'not how to offset the i variable 
       Range("A" & i + 2).EntireRow.Insert
    End If
Next

End Sub
19 голосов
/ 18 ноября 2011

РЕДАКТИРОВАТЬ Сводка и рекомендации

Использование конструкции for each cell in range само по себе не медленно. медленный медленный - это повторный доступ к Excel в цикле (будь то чтение или запись значений ячеек, формат и т. Д., Вставка / удаление строк и т. Д.).

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

Итак, некоторые общие рекомендации:

  1. keepэто просто на первый взгляд.Если результат слишком медленный для ваших нужд, тогда оптимизируйте
  2. , сосредоточьтесь на оптимизации содержимого цикла
  3. , а не просто предполагайте, что цикл необходим.Иногда существуют альтернативы
  4. , если вам нужно использовать значения ячеек (много) внутри цикла, загрузить их в массив вариантов вне цикла.
  5. хороший способ избежать сложности со вставками -чтобы зациклить диапазон снизу вверх
    (for index = max to min step -1)
  6. , если вы не можете сделать это, и ваш «вставить строку здесь и там» не слишком много, попробуйте перезагрузить массив после каждогоinsert
  7. Если вам нужен доступ к свойствам ячейки, отличным от value, вы застряли со ссылками на ячейки
  8. Чтобы удалить несколько строк, рассмотрите возможность создания ссылки на диапазон для диапазона нескольких областей вцикл, затем удалите этот диапазон за один раз после цикла

например (не проверено!)

Dim rngToDelete as range
for each rw in rng.rows
    if need to delete rw then

        if rngToDelete is nothing then
            set rngToDelete = rw
        else
            set rngToDelete = Union(rngToDelete, rw)
        end if

    endif
next
rngToDelete.EntireRow.Delete

Исходное сообщение

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

набор тестовых данных: простой список в ячейках A1 .. A1000000 (то есть 1 000 000 строк)

Контрольный пример 1: цикл массива

Dim v As Variant
Dim n As Long

T1 = GetTickCount
Set r = Range("$A$1", Cells(Rows.Count, "A").End(xlUp)).Cells
v = r
For n = LBound(v, 1) To UBound(v, 1)
    'i = i + 1
    'i = r.Cells(n, 1).Value 'i + 1
Next
Debug.Print "Array Time = " & (GetTickCount - T1) / 1000#
Debug.Print "Array Count = " & Format(n, "#,###")

Результат:

Array Time = 0.249 sec
Array Count = 1,000,001

Контрольный пример 2: цикл диапазона

T1 = GetTickCount
Set r = Range("$A$1", Cells(Rows.Count, "A").End(xlUp)).Cells
For Each c In r
Next c
Debug.Print "Range Time = " & (GetTickCount - T1) / 1000#
Debug.Print "Range Count = " & Format(r.Cells.Count, "#,###")

Результат:

Range Time = 0.296 sec
Range Count = 1,000,000

Итак, зацикливание массива происходит на быстрее, но только на 19% - намного меньше, чем я ожидал.

Тест 3: зацикливание массива со ссылкой на ячейку

T1 = GetTickCount
Set r = Range("$A$1", Cells(Rows.Count, "A").End(xlUp)).Cells
v = r
For n = LBound(v, 1) To UBound(v, 1)
    i = r.Cells(n, 1).Value
Next
Debug.Print "Array Time = " & (GetTickCount - T1) / 1000# & " sec"
Debug.Print "Array Count = " & Format(i, "#,###")

Результат:

Array Time = 5.897 sec
Array Count = 1,000,000

Контрольный пример 4: диапазон цикла с ссылкой на ячейку

T1 = GetTickCount
Set r = Range("$A$1", Cells(Rows.Count, "A").End(xlUp)).Cells
For Each c In r
    i = c.Value
Next c
Debug.Print "Range Time = " & (GetTickCount - T1) / 1000# & " sec"
Debug.Print "Range Count = " & Format(r.Cells.Count, "#,###")

Результат:

Range Time = 2.356 sec
Range Count = 1,000,000

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

Итак, вывод , что важнее всего, это то, что вы делаете внутри цикла , и, если скорость действительно имеет значение, протестируйте все опции

FWIW, протестировано в Excel 2010, 32-битная, Win7, 64-битная, все тесты с

  • ScreenUpdating выключен,
  • Calulation ручной,
  • Events отключен.
1 голос
/ 14 марта 2017

Для каждого это намного быстрее, чем для I = 1 до X, по некоторым причинам. Просто попробуйте пройти через тот же словарь,


один раз для каждого Dkey в dDict,


и один раз для Dkey = lbound (dDict.keys) до ubound (dDict.keys)

=> Вы заметите огромную разницу, даже если вы проходите через ту же конструкцию.

...