【VBAリファレンス】VBA入門者が脱初心者へ!Redim徹底マスターで動的配列を自在に操るデータ処理術

スポンサーリンク

概要:VBA動的配列(Redim)の必要性と基本

Excel VBAを学ぶ上で、データの効率的な処理は避けて通れないテーマです。そして、その核心の一つが「配列」の活用にあります。特に、扱うデータ量が事前に決まっていない、あるいは処理の途中で変動するようなシナリオにおいて、静的な配列では対応しきれない場面が多々発生します。ここで登場するのが「動的配列」と、そのサイズを変更するための`Redim`ステートメントです。

静的配列は、宣言時にそのサイズ(要素数)を固定します。例えば、`Dim arr(9) As String`と宣言すれば、10個の文字列を格納する配列が作成され、そのサイズは変更できません。しかし、実際の業務では、データベースから取得するレコード数、ユーザーが入力する行数、あるいは特定の条件を満たすアイテムの数など、処理対象のデータ量が常に一定であるとは限りません。このような状況で静的配列を使おうとすると、最大ケースに合わせて無駄に大きな配列を宣言するか、途中でサイズが足りなくなってエラーが発生するかのいずれかになります。

動的配列は、宣言時にはサイズを指定せず、必要に応じて`Redim`ステートメントを使ってサイズを決定したり、変更したりできる配列です。これにより、メモリを効率的に利用し、プログラムの柔軟性と堅牢性を飛躍的に向上させることが可能になります。`Redim`をマスターすることは、VBAプログラミングにおけるデータ処理の幅を広げ、より高度なアプリケーションを開発するための第一歩と言えるでしょう。

詳細解説:RedimステートメントのメカニズムとPreserveキーワード

`Redim`ステートメントは、動的配列のサイズを再定義するために使用されます。その基本的な構文は非常にシンプルですが、`Preserve`キーワードの有無によってその挙動が大きく異なります。

Redimの基本構文と挙動

動的配列を宣言する際は、まずサイズを指定せずに宣言します。

Dim myArray() As String ‘ サイズを指定せずに動的配列を宣言

この時点では、`myArray`は単なる箱の準備ができただけで、実際には要素を格納するメモリ領域は確保されていません。実際にサイズを定義し、使用可能にするには`Redim`を使います。

Redim myArray(9) ‘ 0から9までの10個の要素を持つ配列として再定義

この`Redim myArray(9)`を実行すると、`myArray`は10個の要素を格納できるString型の配列としてメモリ上に確保されます。もし`myArray`が既にサイズ定義され、データが格納されていた場合、`Redim`を単独で使用すると、それまでのデータはすべて失われ、新しい空の配列が同じサイズで再作成されます。これは、メモリ上の新しい領域に配列が確保されるためです。

Preserveキーワードの重要性

ほとんどの実用的なシナリオでは、配列のサイズを変更する際に、既に格納されているデータを保持したいと考えます。この要件を満たすのが`Preserve`キーワードです。

Redim Preserve myArray(19) ‘ 既存のデータを保持しつつ、配列サイズを10から20に拡張

`Preserve`を使用すると、`Redim`実行前に配列に格納されていたデータが、新しいサイズの配列にコピーされます。これにより、データの消失を防ぎながら配列のサイズを変更できます。ただし、`Preserve`にはいくつかの重要な制約があります。

1. **データ型の変更は不可**: `Preserve`を使用する場合、配列のデータ型を変更することはできません。宣言時の型を維持する必要があります。
2. **次元数の変更は不可**: 配列の次元数(1次元、2次元など)を変更することはできません。例えば、1次元配列を2次元配列にすることはできません。
3. **最後の次元のみサイズ変更可能**: 最も重要な制約です。多次元配列の場合、`Preserve`を使ってサイズを変更できるのは、常に「最後の次元」のみです。

例を見てみましょう。

Dim multiArray(,) As String ‘ 2次元動的配列を宣言

Redim multiArray(1, 4) ‘ 2行5列の配列として初期化 (0-1, 0-4)
multiArray(0, 0) = “A”
multiArray(1, 4) = “Z”

‘ Preserveを使って最後の次元(列)を拡張することは可能
Redim Preserve multiArray(1, 9) ‘ 2行10列に拡張。データは保持される
‘ multiArray(0, 0) は “A” のまま

‘ Preserveを使って最初の次元(行)を変更しようとするとエラー
‘ Redim Preserve multiArray(2, 4) ‘ 実行時エラーが発生

この制約は、VBAがメモリ上で多次元配列をどのように管理しているかに関連します。`Preserve`は既存のデータを新しい領域にコピーする機能であり、多次元配列の途中の次元を変更すると、データの連続性が崩れるため、この操作が許可されていません。

配列の初期化と解放:Eraseステートメント

動的配列が不要になった場合、または配列の要素をすべて初期化したい場合は、`Erase`ステートメントを使用します。

Erase myArray ‘ myArrayのすべての要素を解放し、メモリを解放する

`Erase`を実行すると、配列の要素に割り当てられていたメモリが解放され、配列はサイズが未定義の状態に戻ります。再度使用するには、`Redim`で改めてサイズを定義する必要があります。静的配列に対して`Erase`を使用すると、要素はデフォルト値(数値なら0、文字列なら””、オブジェクトならNothing)にリセットされますが、メモリは解放されません。

UBound, LBound関数との連携

`Redim Preserve`を使って配列を動的に拡張する際、現在の配列のサイズを知ることは非常に重要です。`UBound`関数は配列の指定された次元の最大インデックスを返し、`LBound`関数は最小インデックスを返します。

Dim myDynamicArray() As Long
Dim i As Long

‘ 初期サイズを定義
Redim myDynamicArray(0)

For i = 1 To 10
‘ 現在の配列の最大インデックスを取得
Dim currentMaxIndex As Long
currentMaxIndex = UBound(myDynamicArray)

‘ 配列の最後に新しい要素を追加するためにサイズを1つ増やす
Redim Preserve myDynamicArray(currentMaxIndex + 1)

‘ 新しい要素に値を代入
myDynamicArray(currentMaxIndex + 1) = i * 10
Next i

‘ 結果の表示 (UBoundは現在の最大インデックス、つまり要素数-1)
For i = LBound(myDynamicArray) To UBound(myDynamicArray)
Debug.Print “myDynamicArray(” & i & “) = ” & myDynamicArray(i)
Next i

この例では、ループ内で`UBound`を使って現在の配列サイズを確認し、`Redim Preserve`でその都度1要素ずつ拡張しています。

サンプルコード:動的配列Redimの実践的活用

以下に、`Redim`と`Preserve`を使った具体的なVBAコード例を示します。

例1:基本的なRedimとPreserveの使用

Sub RedimBasicExample()
Dim myArray() As String ‘ 動的配列の宣言

‘ 最初に5つの要素を持つ配列として定義
Redim myArray(4) ‘ インデックス0から4まで
Debug.Print “— 初期定義 (サイズ: ” & (UBound(myArray) – LBound(myArray) + 1) & “) —”
myArray(0) = “Apple”
myArray(1) = “Banana”
myArray(2) = “Cherry”

‘ 現在の配列の内容を表示
Dim i As Long
For i = LBound(myArray) To UBound(myArray)
If myArray(i) <> “” Then
Debug.Print “myArray(” & i & “) = ” & myArray(i)
Else
Debug.Print “myArray(” & i & “) = (Empty)”
End If
Next i

‘ PreserveなしでRedimするとデータは失われる
Redim myArray(9) ‘ 0から9までの10個の要素。既存データはクリアされる
Debug.Print vbCrLf & “— Redim (データ消失) (サイズ: ” & (UBound(myArray) – LBound(myArray) + 1) & “) —”
For i = LBound(myArray) To UBound(myArray)
If myArray(i) <> “” Then
Debug.Print “myArray(” & i & “) = ” & myArray(i)
Else
Debug.Print “myArray(” & i & “) = (Empty)”
End If
Next i

‘ PreserveありでRedimするとデータは保持される
Redim Preserve myArray(14) ‘ 0から14までの15個の要素。既存のEmptyデータは保持
Debug.Print vbCrLf & “— Redim Preserve (サイズ: ” & (UBound(myArray) – LBound(myArray) + 1) & “) —”
myArray(0) = “Grape” ‘ 新しいデータを上書き
myArray(10) = “Kiwi” ‘ 新しく追加された要素にデータを格納
For i = LBound(myArray) To UBound(myArray)
If myArray(i) <> “” Then
Debug.Print “myArray(” & i & “) = ” & myArray(i)
Else
Debug.Print “myArray(” & i & “) = (Empty)”
End If
Next i

‘ 配列を解放
Erase myArray
Debug.Print vbCrLf & “— Erase後の状態 —”
‘ Debug.Print UBound(myArray) ‘ Erase後はエラーになるためコメントアウト
Debug.Print “配列は解放されました。”

End Sub

例2:動的にデータを収集し配列に格納する

この例では、ワークシートのA列からデータが入力されているセルを順に読み込み、そのデータを動的配列に格納していきます。データがいくつあるか事前に分からない状況で非常に役立ちます。

Sub CollectDataIntoDynamicArray()
Dim dataArray() As String ‘ データを格納する動的配列
Dim cell As Range
Dim lastRow As Long
Dim arrayIndex As Long

‘ アクティブシートのA列最終行を取得
With ThisWorkbook.Sheets(“Sheet1”) ‘ 実際のシート名に合わせて変更してください
lastRow = .Cells(.Rows.Count, “A”).End(xlUp).Row

If lastRow = 1 And IsEmpty(.Cells(1, “A”).Value) Then
MsgBox “A列にデータがありません。”, vbInformation
Exit Sub
End If

‘ 配列の初期化(最初の要素の準備)
‘ LBoundを0とする場合、最初の要素はarrayIndex=0となる
Redim dataArray(0)
arrayIndex = 0

‘ A列のデータがあるセルをループ
For Each cell In .Range(“A1:A” & lastRow)
If Not IsEmpty(cell.Value) Then
‘ 配列のサイズを1つ拡張し、既存データを保持
If arrayIndex > UBound(dataArray) Then ‘ 最初の要素以降の拡張
Redim Preserve dataArray(UBound(dataArray) + 1)
End If

‘ データを配列に格納
dataArray(arrayIndex) = CStr(cell.Value)
arrayIndex = arrayIndex + 1
End If
Next cell
End With

‘ 最終的な配列サイズに合わせてRedim Preserveで調整(余分な要素を削除)
‘ データが全くない場合はUBound(dataArray)が-1になる可能性があるためチェック
If arrayIndex > 0 Then
Redim Preserve dataArray(arrayIndex – 1)
Else
Erase dataArray ‘ データが一つも格納されなかった場合
End If

‘ 収集したデータをDebug.Printで表示
Debug.Print “— 収集されたデータ —”
If arrayIndex > 0 Then
For arrayIndex = LBound(dataArray) To UBound(dataArray)
Debug.Print “dataArray(” & arrayIndex & “) = ” & dataArray(arrayIndex)
Next arrayIndex
Else
Debug.Print “配列は空です。”
End If

End Sub

このコードでは、初期に`Redim dataArray(0)`で配列を最小サイズで定義し、その後は`Redim Preserve dataArray(UBound(dataArray) + 1)`で1要素ずつ拡張しています。最後に、実際に格納されたデータ数に合わせて配列を調整(余分な要素があれば削除)しています。

実務アドバイス:パフォーマンスとメモリ管理の最適化

`Redim Preserve`は非常に便利ですが、その使い方によってはパフォーマンスに大きな影響を与える可能性があります。実務で動的配列を扱う際の重要なアドバイスをいくつかご紹介します。

1. **Redim Preserveの多用は避ける**:
`Redim Preserve`は、既存の配列内容を新しいメモリ領域にコピーする処理を伴います。そのため、ループ内で`Redim Preserve`を頻繁に呼び出して1要素ずつ拡張するようなコードは、データ量が増えるほど処理速度が著しく低下します。
* **最適化戦略**:
* **バッチ拡張**: データをまとめて取得し、一括で`Redim`する。
* **初期サイズの見積もり**: ある程度の最大値を予測し、最初に大きめに`Redim`しておく。足りなくなったら、現在のサイズの倍にするなど、ある程度のブロック単位で`Redim Preserve`を使って拡張する。
* **代替コレクション**: 配列のサイズ変更が非常に頻繁で、データ順序が重要でない場合は、`Collection`オブジェクトや`Scripting.Dictionary`オブジェクト(キーと値のペアを扱う場合)の利用も検討しましょう。これらは内部的に動的なサイズ変更を効率的に処理します。

2. **メモリ管理とEraseの活用**:
大規模なデータを扱う場合、不要になった動的配列は速やかに`Erase`で解放する習慣をつけましょう。VBAはガベージコレクション機能を持っていますが、明示的に解放することで、メモリ消費を抑え、安定した動作に繋がります。特に、プロシージャの終了時に自動的に解放されるとは限らない、モジュールレベルで宣言された配列や、長時間稼働

タイトルとURLをコピーしました