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) ←これすき