Ускорение VBA для цикла с массивом/словарем?

#excel #vba

Вопрос:

Я пытаюсь ускорить цикл For, так как для некоторых листов с большим количеством строк / столбцов требуется много времени. Уже пробовал что-то с массивом и словарем, но я не понимаю. Как можно оптимизировать этот код для проверки содержимого ячеек и вставки, если верно, нового вычисленного содержимого в другие ячейки? Большое спасибо!

     Pos = 1
    lngWS = ws_Table.ListRows.Count
    
    For Pos = Pos To lngWS
        If ws_Table.DataBodyRange(Pos, Column_Date).Value2 <> vbNullString And _
           ws_Table.DataBodyRange(Pos, Column_Verify).Value2 <> "OK" Then

            ws_Table.DataBodyRange(Pos, Column_Year).Value2 = Year(ws_Table.DataBodyRange(Pos, Column_Date).Value2)
            ws_Table.DataBodyRange(Pos, Column_Month).Value2 = Format(ws_Table.DataBodyRange(Pos, Column_Date).Value2, "MMMM")

            ws_Table.DataBodyRange(Pos, Column_Compare_1).FormulaLocal = "=1 1"
            ws_Table.DataBodyRange(Pos, Column_Compare_1).Value2 = ws_Table.DataBodyRange(Pos, Column_Compare_1).Value2
            ws_Table.DataBodyRange(Pos, Column_Compare_2).FormulaLocal = "=2 2"
            ws_Table.DataBodyRange(Pos, Column_Compare_2).Value2 = ws_Table.DataBodyRange(Pos, Column_Compare_2).Value2

        End If
    Next Pos
 

Ответ №1:

Чтобы ускорить работу с данными в диапазоне, гораздо быстрее скопировать данные из диапазона в массив, поработать с массивом, а затем скопировать данные обратно в диапазон.

Это работает для диапазонов с более чем одной ячейкой:

 Dim Lst as Variant ' The array
Dim Idx as Long ' The row index

' First copy the range to an array
Lst = ws_Table.DataBodyRange
For Idx = LBound(Lst) To UBound(Lst)
    ' Change the rows in the array here, for example:
    '     Lst(Idx, 1) = Lst(Idx, 1) * 3
    '
    ' Be aware that you might need to recreate your formulas, as 
    ' it is the results of the forumalas that are copied to the array:
    '     Lst(Idx, 2) = "=1 1"
Next Idx
' Then copy the array back to the range
ws_Table.DataBodyRange = Lst
 

Причина, по которой работа с массивом выполняется быстро, заключается в том, что это структура данных в памяти, а объект listobject, диапазон или ячейка являются COM-объектом. Для ссылки на COM-объект требуется много времени.

Метод, который я рекомендую, ссылается только на COM-объект 2 или 3 раза, в то время как метод, используемый в вашем примере, или решение, предложенное Тимом Уильямсом, ссылается на COM-объект несколько раз для каждой строки в диапазоне.

Ответ №2:

Это может быть немного быстрее:

 Dim dtVal, verVal, pos As Long
'...
'...

Application.ScreenUpdating = False
For pos = 1 To ws_Table.ListRows.Count
    With ws_Table.DataBodyRange.Rows(pos)
        
        dtVal = .Cells(Column_Date).Value2
        verVal = .Cells(Column_Verify).Value2
        
        If Len(dtVal) > 0 And verVal <> "OK" Then

            .Cells(Column_Year).Value2 = Year(dtVal)
            .Cells(Column_Month).Value2 = Format(dtVal, "MMMM")

            With .Cells(Column_Compare_1)
                .FormulaLocal = "=1 1"
                .Value = .Value
            End With
            With .Cells(Column_Compare_2)
                .FormulaLocal = "=2 2"
                .Value2 = .Value2
            End With
        End If 'have date and not "OK"
    End With
Next pos