VBA: большие данные и использование массивов - PullRequest
0 голосов
/ 26 апреля 2018

Так что я работаю с большими 28 000 строк плюс данные +. Плюс 5 других таблиц для перекрестных ссылок.

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

Или правильно сказать, что массивы просто быстрее, чем сказать ...

Worksheet.range("A1").Value=AOtherWorksheet.range("A1").Value

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

Любые другие комментарии к трюкам для автоматизации больших электронных таблиц приветствуются, но в основном фокусируются на понимании этого лакомого кусочка.

1 Ответ

0 голосов
/ 27 апреля 2018

Я думаю, что магия вызвана сложностью - каждая клетка несет с собой много "багажа"

Сотни настроек для своего окружения, и большинство из них касается форматирования ячеек

  • Height, Width, RowHeight, ColumnWidth, Column, Row, Font, IndentLevel и т. Д.

Чтобы увидеть все свойства, обратите внимание на окно просмотра для Sheet1.Range("A1")

(свойства с + рядом с ними являются сложными объектами со своим набором свойств)

RangeProperties


Основная причина оптимизации с использованием массивов состоит в том, чтобы избежать всего форматирования

Каждая ячейка «знает» обо всех настройках независимо от того, изменены они или нет, и несет весь этот «вес». Большинство пользователей в большинстве случаев заботятся только о значении в ячейке и никогда не касаются форматирования. В редких случаях вы можете застревать, работая напрямую с объектом диапазона, если вам нужно изменить .Borders, .Interior.Color, .Font и т. Д. Каждой отдельной ячейки, и даже в этом случае существуют способы группировки ячеек с одинаковым форматированием и изменения атрибуты всей группы одновременно


.

Продолжить аналогию с багажом (и это немного растягивает): в аэропорту, если мне нужно пополнить ручку для пассажира "Джон Доу" из его багажа уже в самолете, в подсобном помещении в в аэропорту я смогу это сделать (у меня есть вся необходимая информация), но мне понадобится время, чтобы идти туда-сюда с багажом. И для одного пассажира это может быть сделано за разумное количество времени, но сколько времени потребуется, чтобы пополнить 20 тыс. Ручек, или 100 тыс., Или миллион? (ОДИН-ПО-ОДИН)

Я рассматриваю взаимодействие Range <-> VBA одинаково: работать с отдельными ячейками по одной - это все равно, что перевозить каждый отдельный багаж на миллион пассажиров в подсобное помещение в задней части аэропорта. , Вот что делает это утверждение:

Sheet1.Range("A1:A1048576").Value = Sheet2.Range("A1:A1048576").Value

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


.

Копирование объекта диапазона в массив изолирует одно из свойств для каждой ячейки - его Value («ручка») от всех остальных настроек (Excel чрезвычайно эффективен в этом отношении) , Теперь у нас есть массив только значений, и никаких других настроек форматирования. Измените каждое значение отдельно в памяти, затем поместите их все обратно в объект диапазона:

Dim arr as Variant

arr = Sheet2.Range("A1:A1048576")    'Get all values from Sheet2 into Sheet1

Sheet1.Range("A1:A1048576") = arr

.

Здесь также отличаются параметры копирования / вставки:

Sheet2.Range("A1:A1048576").Copy

Sheet1.Range("A1:A1048576").PasteSpecial xlPasteAll

.

Timers for Rows: 1,048,573

 xlPasteAll                          - Time: 0.629 sec; (all values + all formatting)
 xlPasteAllExceptBorders             - Time: 0.791 sec
 xlPasteAllMergingConditionalFormats - Time: 0.782 sec; (no merged cells)
 xlPasteAllUsingSourceTheme          - Time: 0.791 sec
 xlPasteColumnWidths                 - Time: 0.004 sec
 xlPasteComments                     - Time: 0.000 sec; (comments test is too slow)
 xlPasteFormats                      - Time: 0.497 sec; (format only, no values, no brdrs)
 xlPasteFormulas                     - Time: 0.718 sec
 xlPasteFormulasAndNumberFormats     - Time: 0.775 sec
 xlPasteValidation                   - Time: 0.000 sec
 xlPasteValues                       - Time: 0.770 sec; (conversion from formula to val)
 xlPasteValuesAndNumberFormats       - Time: 0.634 sec

.

Другим аспектом, помимо массивов, являются типы индексов для структур данных

Для большинства ситуаций допустимы массивы, но когда требуется лучшая производительность, существуют объекты Dictionary и Collection

  • Одна неэффективность массива заключается в том, что для поиска элементов нам нужно перебирать каждый из них

  • Более удобным вариантом может быть доступ к определенным элементам, намного быстрее


Dim d As Object                               'Not indexed (similar to linked lists)
Set d = CreateObject("Scripting.Dictionary")  'Native to VB Script, not VBA

d.Add Key:="John Doe", Item:="31"             'John Doe - 31 (age); index is based on Key
d.Add Key:="Jane Doe", Item:="33"
Debug.Print d("Jane Doe")                     'Prints 33

В словарях также есть очень полезный и быстрый метод проверки элементов d.Exists("John Doe"), который возвращает True или False без ошибок (но в коллекциях нет). С массивом вам придется перебрать потенциально все элементы, чтобы узнать

Я думаю, что один из самых быстрых способов извлечь уникальные значения для больших столбцов - это объединить массивы и словари


Public Sub ShowUniques()
    With Sheet1.UsedRange
        GetUniques .Columns("A"), .Columns("B")
    End With
End Sub

Public Sub GetUniques(ByRef dupesCol As Range, uniquesCol As Range)
    Dim arr As Variant, d As Dictionary, i As Long, itm As Variant

    arr = dupesCol

    Set d = CreateObject("Scripting.Dictionary")
    For i = 1 To UBound(arr)
        d(arr(i, 1)) = 0    'Shortcut to add new items to dictionary, ignoring dupes
    Next

    uniquesCol.Resize(d.Count) = Application.Transpose(d.Keys)

    'Or - Place d itms in new array (resized accordingly), and place array back on Range
    '    ReDim arr(1 To d.Count, 1 To 1)
    '    i = 1
    '    For Each itm In d
    '        arr(i, 1) = itm
    '        i = i + 1
    '    Next
    '    uniquesCol.Resize(d.Count) = arr
End Sub

From:   Col A    To: Col B
          1            1
          2            2
          1            3
          3
  • Словари не принимают дубликаты ключей, просто игнорируют их
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...