#arrays #vba #excel #matrix
#массивы #vba #excel #матрица
Вопрос:
У меня есть большой диапазон данных в Excel, которые я хотел бы преобразовать в массив для определяемой пользователем функции. Диапазон составляет 2250 x 2250. Для анализа каждой ячейки в цикле for требуется слишком много времени, и она слишком велика, чтобы быть назначенной массиву с помощью этого метода:
dim myArr as Variant
myArr = range("myrange")
Просто мозговой штурм здесь, было бы более эффективно анализировать в каждом столбце и объединять массивы? Есть идеи?
Спасибо
Комментарии:
1. Я просто настраиваю рабочую книгу с данными в диапазоне от a1 до chn2250 (диапазон 2250X2250) и считываю ее в альтернативный массив с именем «x», используя
x= range("a1:chn2250").Value2
then, просто чтобы убедиться, что это сработалоdebug.print x(2,2)
. Вся операция заняла меньше секунды. Итак, я бы предложил альтернативный массив.2. Попробуйте добавить
.Value
или.Value2
, как предложил @Kyle, к тому, что у вас было выше.3. Я явно не указывал
.Value2
, но это наверняка повышает производительность.4.
2250×2250×(8 bytes per double) = 38.6 Mb
который вообще не должен нагружать память.5. @ja72 откуда вы знаете, что они двойные? что, если все они представляют собой строки длиной 1024? Это вариантный массив… Вариантные переменные с числами требуют 16 байт памяти. Переменным типа данных Variant с символами обычно требуется 22 байта памяти плюс память, требуемая для строки. Это даже не говорит о памяти, используемой для самой структуры массива.
Ответ №1:
Вы почти на месте.
Необходимый вам код:
Dim myArr as Variant
myArr = range("myrange").Value2
Обратите внимание, что я использую .Value2
свойство диапазона, а не просто «Значение», которое считывает форматы и настройки локали и, вероятно, искажает любые даты
Обратите внимание, также, что я не удосужился Redim
указать размеры массива: свойства Value и Value2 представляют собой 2-мерный массив (от 1 до Rowcount, от 1 до Col Count)… Если только это не одна ячейка, которая будет скалярным вариантом, который нарушает любой нисходящий код, который ожидал массив. Но это не ваша проблема с известным диапазоном 2250 x 2250.
Если вы отмените операцию и запишете массив обратно в диапазон, вам нужно будет установить размер принимаемого диапазона в точности на размеры массива. Опять же, это не ваша проблема с вопросом, который вы задали: но две операции обычно идут вместе.
Общий принцип заключается в том, что каждое «попадание» на рабочий лист занимает около двадцатой доли секунды — некоторые машины намного быстрее, но у всех бывают плохие дни — и «попадание» или чтение одной ячейки в переменную почти точно такое же, как чтение диапазона в семь миллионов ячеекв массив вариантов. Оба в несколько миллионов раз быстрее, чем чтение этого диапазона в одной ячейке за раз.
В любом случае, вы также можете считать любую операцию в VBA выполненной за нулевое время, как только вы выполнили «чтение» и прекратили взаимодействие с рабочим листом.
Все числа очень приблизительны, но общие принципы будут соблюдаться вплоть до того момента, когда вы начнете выделять массивы, которые не поместятся в рабочей памяти, и, опять же, это не ваша проблема сегодня.
Помните Erase
о варианте массива, когда закончите, вместо того, чтобы полагаться на то, что он выходит за рамки: это будет иметь значение при таком размере диапазона.
Комментарии:
1. Привет, Нил, большое спасибо за подробное объяснение. Я не думал использовать Value2, и, похоже, это должно сработать. Тем не менее, я могу столкнуться с объемом памяти. Макрос теперь прерывается, сообщая, что ему не хватает памяти. Размер листа составляет 70 КБ, поскольку каждая ячейка в матрице имеет функцию. Есть ли какие-либо другие рекомендации, которые вы можете дать для меня? Если нет, я могу попытаться удалить некоторую избыточную информацию.
2. @Daniel — Макрос ломается в
arr = Range.Value2
инструкции? Есть предел тому, что вы можете сделать, но объявление переменной объекта range и ее использование могут помочь; как и настройкаApplication.Calculation = xlCalculationManual
3. @Daniel — если эти шаги не решают проблему, ваш следующий шаг — переопределить массив вручную, затем передать в него значение диапазона:
Redim arr(1 to rng.Rows.Count, 1 to rng.Columns.Count
и затем попробоватьarr = rng.Value2
… И следующим шагом является загрузка диапазона порциями — либо по тысяче строк, либо по тысяче столбцов или одновременно. Также: вы используетеOption.Explicit
или параметр «Требовать объявления переменной» в VBA?4. @NigelHeffernan при добавлении
.Value2
похоже, что это ускоряет код примерно 10% …it ничего не делает для решения проблемы. К вашему сведению, использование.Formula
заняло примерно в 2 раза больше времени (с моим набором данных), что имеет смысл, поскольку строки больше.5. @Profex — Value2 — это (или должно быть) первое, что вы пытаетесь сделать при чтении значений в диапазоне; все остальное будет обременять вас зависимостями от локальных форматов. Канонический пример — использование . Значение 2 для извлечения базового серийного номера даты.
Ответ №2:
Это работает нормально.
Sub T()
Dim A() As Variant
A = Range("A2").Resize(2250, 2250).Value2
Dim i As Long, j As Long
For i = 1 To 2250
For j = 1 To 2250
If i = j Then A(i, j) = 1
Next j
Next i
Range("A2").Resize(2250, 2250).Value2 = A
End Sub
Комментарии:
1. Спасибо, это интересный трюк! … Можете ли вы проверить, работает ли объявление переменной диапазона, установка для нее значения monster range и заполнение массива из этой переменной диапазона? Эта ошибка может зависеть от машины, но ближайшей причиной является то, что VBA делает дикие предположения о памяти, которую ему необходимо выделить, когда он пытается интерпретировать
Range( [some string we'll parse at runtime] ).Value2
вместо хорошо управляемого объекта с помощью VTable или (как вы нам показали) функции, возвращающей диапазон, с четко определенным размером.2. Как
Set r = Range("..").Resize(..)
и тогдаvals = r.Values2
? Это то же самое, что и выше.3. — Не совсем. С точки зрения того, для чего предназначен код, оба подхода одинаковы. Но достаточно ли умен компилятор (и поведение во время выполнения), чтобы распознать, что ваш объект r диапазона имеет известные размеры во время компиляции? Или реализация для чтения измерений переменной диапазона отличается (с меньшей вероятностью делать дикие предположения и переоценивать при выделении памяти) от механизма для чтения его из метода диапазона с произвольной адресной строкой?
4. Я бы предположил, что массив в
arr = Range("A1").Value
выделяется до того, как будет проанализирована адресная строка — возможно, до размеров всего листа! — и затем переопределяется, когда количество строк и столбцов известно. Но объявленный объект диапазона не имеет.Value
свойства для передачи варианту массива, пока не будут известны размеры, и механизм выполнения должен выполнять задачи в правильной последовательности. Конечно, вполне возможно, что сообщение об ошибке на самом деле не «Не хватает памяти» : это может быть «Ой! Мне нужно выделить память прямо сейчас , но одно из чисел отсутствует »5. Я почти уверен, что компилятор знает, какой размер диапазона is…it это не имеет значения только потому, что это явно прописано для нас, людей, для чтения. Он не угадывает размер и не пытается выделить какую-либо память, пока не дойдет до этой строки …. так работает VBA AFAIK.
Ответ №3:
Я думаю, что лучшими вариантами являются:
- Попробуйте ограничить данные разумным числом, скажем, 1 000 000 значений за раз.
- Добавьте некоторую обработку ошибок, чтобы перехватить
Out of Memory
ошибку, а затем повторите попытку, но уменьшите размер вдвое, затем на треть, на четверть и т. Д., Пока это не сработает.
В любом случае, если мы используем наборы данных порядка 5 000 000 значений, и вы хотите убедиться, что программа будет запущена, вам нужно будет настроить код, чтобы разделить данные.