RCIE-ジャンクのコード屋

主に自分のためにコーディングのTIPSを蓄積しています。

VBAの可変長配列 Collection / ReDim Preserve

要点

  • VBAで、要素を追加すると伸長する配列みたいなものといえば、Collectionです。
  • 配列のメモリ再割り当てReDim Preserveを使っても、可変長の配列を実現できます。面倒だけど動作は速い。

線形リスト Collection

Collectionは線形リストです。以下のようなコードで簡単に要素を追加・アクセス・削除できます。

Dim c果物 As New Collection

c果物.Add "りんご"    '要素の追加
c果物.Add "みかん"
c果物.Add "バナナ"

Debug.Print c.果物(1)    'りんご ※要素の番号は1から始まる
Debug.Print c.果物(2)    'みかん
Debug.Print c.果物(3)    'バナナ

c果物.Remove 2    'みかんを削除
弱点1:書き換えができない

大変不便なことに、各要素を書き換えることができません。罠です。

c果物(2) = "オレンジ"    '※できません。
'    ↓ 消してから追加するしかない…
c果物.Remove 2
c果物.Add "オレンジ", After:= 1
弱点2:後ろの方のアクセスがやや遅い

線形リストの特徴ですが、前から順番に要素を辿らないといけないので、後ろの方の要素のアクセスが遅いです。
万単位の要素数の場合には無視できなくなります。逆に、千単位の要素数なら速度の違いは無視していいです。
For Eachを使って前から順次にアクセスする場合には、後ろでも遅くなりません。

Dim c巨大 As New Collection    '巨大なリスト(1~20000を入れる予定)
Dim i As Long
For i = 1 To 20000
    c巨大.Add i
Next

Dim n合計 As Long    '全要素を合計する
Dim n
For Each n In c巨大
    n合計 = n合計 + n
Next

Debug.Print n合計    '200010000

遅いバージョン(Forを使う)

Dim c巨大 As New Collection    '巨大なリスト(1~20000を入れる予定)
Dim i As Long
For i = 1 To 20000
    c巨大.Add i
Next

Dim n合計 As Long    '全要素を合計する
Dim j As Long
For j = 1 To 20000
    n合計 = n合計 + c巨大(j)
Next

Debug.Print n合計    '200010000

配列メモリの再割り当て ReDim Preserve

普通の配列では、要素の追加はできません。ReDim Preserveを使えば、メモリを再割り当てして可変長を実現できます。

Dim a果物(1 To 3) As String    '要素3つの配列
a果物(1) = "りんご"
a果物(2) = "みかん"
a果物(3) = "バナナ"    '使えるのは、3番目まで

ReDim Preserve a果物(1 To 5) As String    '中身を保ったまま拡張
a果物(4) = "いちご"
a果物(5) = "メロン"    '5番目まで使えるようになった
拡張するなら倍の長さで

要素を追加する度にReDim Preserveで拡張すると、動作が遅くなります。
配列がすべて埋まったら、思い切って倍の長さを確保しましょう。

Dim a配列(1 To 100) As Long   '容量100の整数配列(1~10000を入れる予定)
Dim n配列長 as Long    '配列の実際の長さも保持しておく(初期値=0)

Dim i as Long
For i = 1 To 10000
    n配列長 = n配列長 + 1
    If n配列長 > UBound(a配列) Then    '長さが足りなくなったら、倍の長さに拡張
        ReDim Preserve a配列(1 To UBound(a配列) * 2)
    End If

    a配列(n配列長) = i
Next
ReDim Preserve a配列(1 To n配列長)    '最後に余った部分を切り落としたら出来上がり

余談:1から始まるインデックス

VBAでセルを配列として取得すると、最初の要素が「1番目」になります。

Dim a
a = Range("A1:B3").Value

Debug.Print a(1, 1)    'A1は a(0, 0) じゃないよ
Debug.Print a(1, 2)    'A2
Debug.Print a(1, 3)    'A3
Debug.Print a(2, 1)    'B1
Debug.Print a(2, 2)    'B2
Debug.Print a(2, 3)    'B3

普通に配列を作ると、0番目から要素が始まってしまい、セル配列や Collection と規則が違うのが気持ち悪いですね。
個人的には、1から始まるように配列を作ることにしています。

Dim a(3) As String    'a(0), a(1), a(2)
Dim a(1 To 3) As String 'a(1), a(2), a(3) ←これすき