Предотвращение дополнительных элементов в динамических массивах VBA - PullRequest
1 голос
/ 17 октября 2008

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

Например, при использовании кода, аналогичного следующему:

While Cells(ndx, 1).Value <> vbNullString
    ReDim Preserve data(1 To (UBound(data) + 1))
    ndx = ndx + 1
Wend

У вас есть дополнительный пустой элемент массива в конце обработки. Хотя это можно устранить с помощью следующего:

ReDim Preserve data(1 To (UBound(data) - 1))

Это не самый лучший способ решения этой проблемы.

Таким образом, есть ли способ предотвратить создание этого дополнительного элемента? Желательно что-то, что не требует дополнительной логики внутри цикла.

Ответы [ 6 ]

3 голосов
/ 17 октября 2008

ReDim Preserve - это относительно дорогая операция, которая, вероятно, не является чем-то, что нужно делать на каждой итерации. Лучше ReDim массив до верхних границ больше, чем вам нужно (возможно, кусками), а затем уменьшить массив до требуемых верхних границ, когда вы закончите.

Кроме того, вы можете изучить другие способы чтения диапазона Excel в массив, например,

  Dim a()
  With Sheet1
    a = .Range(.Range("A1"), .Range("A1").End(xlDown)).Value
  End With
  Debug.Print a(1, 1)

Зацикливание часто очень медленное:)

1 голос
/ 19 марта 2012

Прошло много времени с тех пор, как этот вопрос был задан; однако сегодня у меня была та же проблема, и я решил ее так:

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

Dim data()
Dim i as Long

ReDim data(-1 To -1) ' Empty array. We never use data(-1).

For i = 0 To UBound(data)
    ...
Next i

Если вам нужен массив на основе 1, вы должны сделать это

ReDim data(0 To 0) ' Empty array. We never use data(0).

For i = 1 To UBound(data)
    ...
Next i
1 голос
/ 17 октября 2008

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

Использование коллекции - Хотя этот подход вполне работает в ситуациях, когда вам нужно читать и хранить данные, вы не можете использовать пользовательские типы с коллекцией. Возможность определить ключ элемента полезна, поскольку вы можете использовать его для перекрестных ссылок на две Коллекции; однако в VBA нет способа получить список ключей в Collection , который может быть ограничивающим.

Чтение диапазона Excel в массив - еще один очень приятный подход, но, похоже, он лучше всего работает, когда вы знаете, какие диапазоны будут опережать время. Если вам нужно определить диапазоны на лету, вы можете оказаться в ситуации, когда с коллекцией или массивом ReDim меньшего размера легче работать.

Создание массива на лету с ReDim Preserve - Хотя это может быть довольно простой операцией, с этим связаны две проблемы. Во-первых, ReDim может быть дорогостоящей операцией, поскольку Visual Basic фактически создает новый массив с заданным размером, копирует старый массив, а затем удаляет старый массив (или освобождает его для сборщика мусора в Visual Basic. .СЕТЬ). Поэтому вы хотели бы свести к минимуму количество вызовов в ReDim, если собираетесь работать с очень большими массивами.

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

1 голос
/ 17 октября 2008

VB6 и COM используют сочетание индексации на основе 0 и 1 (массивы основаны на 0, за исключением случаев, когда вы изменяете это с помощью Option Base или объявляете их иным образом).

Коллекции COM обычно основаны на 1 в более ранних объектных моделях COM, но иногда основаны на 0 ...

Данные Dim / Redim (от 1 до N) - это массив из N элементов, проиндексированных от 1 до N

Данные Dim / Redim (от 0 до N-1) - это массив из N элементов, проиндексированных от 0 до N-1

Данные Dim / Redim (N) - это массив из N + 1 элементов, проиндексированных от 0 до N (если Option Base равен 0)

Последний случай, который иногда сбивает с толку, данные (N) обычно означают данные (от 0 до N), которые представляют собой массив из N + 1 элементов.

Лично я всегда объявляю массивы явно как (от 0 до N-1) и не полагаюсь на Option Base, которая более знакома разработчикам, использующим более одного языка.

Существует один крайний случай: VBA не поддерживает массивы нулевой длины, у вас всегда должен быть хотя бы один элемент (для каждого измерения в многомерных массивах). Поэтому наименьший массив, который вы можете объявить, - это данные (от 0 до 0) или данные (от 1 до 1) с одним элементом.

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

ReDim data(1 To 1)    
While Cells(ndx, 1).Value <> vbNullString    
    ReDim Preserve data(1 To (UBound(data) + 1))    
    ndx = ndx + 1
Wend

Вместо этого (оставляя в стороне соображения эффективности вызова ReDim Preserve в цикле), вы должны использовать:

ReDim data(1 To 1)    
nStartIndex = ndx
While Cells(ndx, 1).Value <> vbNullString    
    ' On the first iteration this does nothing because
    ' the array already has one element
    ReDim Preserve data(1 To ndx - nStartIndex + 1)    
    ndx = ndx + 1
Wend
1 голос
/ 17 октября 2008

Массивы Visual Basic начинаются с нуля. Это можно изменить с помощью оператора Option Base.

С вашими массивами, дополнительные элементы, потому что вы делаете UBound() + 1, UBound уже даст вам правильный номер. Если в массиве 5 элементов, UBound будет 5. Но последний индекс будет 4, поэтому ReDim to UBound даст вам массив размером + 1.

Поскольку использование массивов в любом случае затруднительно (то есть в VBA), а ReDim Preserve фактически является операцией копирования массива в новый массив фиксированного размера, я рекомендую вам использовать Коллекции везде, где вы можете. Их намного проще перебирать (For Each ... In ...) и гораздо эффективнее добавлять, находить и удалять элементы.

' creation ' 
Dim anyValue as Variant
Dim c as New Collection

' and adding values '
c.Add anyValue, strKey

' iteration '
For Each anyValue in c
  Debug.Print anyValue
Next c

' count values '
Debug.Print c.Count

' element deletion '
c.Delete strKey

(Вы можете использовать Scripting.Dictionary из VBScript для дополнительного удобства, но сначала вам нужно сослаться на это.)

Вы также можете иметь несколько измерений Коллекций, просто помещая их друг в друга.

0 голосов
/ 17 октября 2008

Конечно, прошло уже много времени с тех пор, как я сделал классический VB6, и мой опыт работы с VBA стал еще более грубым ... если память служит, синтаксис массива для VB отличается от того, что основа равна 1 вместо 0. Синтаксис также говорит, что указанный вами размер не обозначает общее количество элементов в массиве, а скорее последний индекс, который можно адресовать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...