Excel VBA | 動的配列

VBA
スポンサーリンク

Excel VBAの動的配列をやさしく解説

夜遅くまでえらい。動的配列は「必要なだけ、あとから箱の数を増減できる配列」。最初に大きさが決まっていなくても、実際のデータ量に合わせて伸縮できます。


動的配列ってなに?

  • 基本イメージ: 最初はサイズ未定の「伸び縮みする箱の並び」。
  • 宣言のしかた: サイズを決めずに Dim arr() As 型 と書く。あとで ReDim で大きさを決める。
  • メリット: 無駄なメモリを使わない。入力データが何件あるか分からない場面で強い。
  • 注意点: ReDim し直すと、中身が消える。中身を残したいときは ReDim Preserve を使う(最後の次元だけサイズ変更可)。

基本パターン(1次元)

例題1:ユーザー入力の件数に応じて配列を拡張

Sub DynamicArray_Basics()
    Dim names() As String   'サイズ未定
    Dim count As Long
    Dim i As Long

    '件数を決める(仮に入力やシートから取得した数とする)
    count = 3

    'ここでサイズを確定(0~count-1 で count 個)
    ReDim names(count - 1)

    'データ代入
    names(0) = "佐藤"
    names(1) = "鈴木"
    names(2) = "高橋"

    '表示
    For i = 0 To UBound(names)
        Debug.Print "名前" & i & ": " & names(i)
    Next i
End Sub
VB
  • ポイント: Dimではサイズ未定、ReDimで初めて作る。上限取得は UBound(arr)
  • 0スタート慣れ: ReDim names(count - 1) で count 個分になる。

段階的に増やす方法

例題2:1件ずつ読み込みながら配列を伸ばす(Preserveで中身保持)

Sub DynamicArray_GrowStepByStep()
    Dim items() As String
    Dim n As Long

    '最初は空の配列(サイズ0にしておく)
    ReDim items(0)
    items(0) = "Apple"
    n = 1

    '新しい要素を追加するたびに、サイズを1つ増やす
    n = n + 1
    ReDim Preserve items(n - 1)
    items(n - 1) = "Banana"

    n = n + 1
    ReDim Preserve items(n - 1)
    items(n - 1) = "Cherry"

    '確認
    Dim i As Long
    For i = 0 To UBound(items)
        Debug.Print i, items(i)
    Next i
End Sub
VB
  • 注意: ReDimだけだと中身が消える。追加式は必ず ReDim Preserve
  • 非効率に見えるけど: 少量ならOK。大量ならまとめて拡張(例えば+10ずつ)すると速い。

要素追加をもう少し効率的に

例題3:容量をまとめて増やす「バッファ」方式

Sub DynamicArray_BufferedAppend()
    Dim items() As String
    Dim size As Long      '確保済みサイズ
    Dim count As Long     '実際の要素数
    Dim i As Long

    size = 0
    count = 0

    '最初に10個分まとめて確保
    size = 10
    ReDim items(size - 1)

    '10件追加してみる
    For i = 1 To 10
        items(count) = "Item" & count
        count = count + 1
    Next i

    'さらに5件追加したい → 容量不足なら20個に拡張
    If count + 5 > size Then
        size = size * 2       'まとめて倍にする
        ReDim Preserve items(size - 1)
    End If

    '5件追加
    Dim k As Long
    For k = 1 To 5
        items(count) = "Extra" & count
        count = count + 1
    Next k

    '出力(count までが有効データ)
    For i = 0 To count - 1
        Debug.Print items(i)
    Next i
End Sub
VB
  • メリット: ReDim Preserve の回数を減らせて高速。
  • 使い分け: 小規模なら例題2、大規模ならバッファ方式。

2次元の動的配列

  • 基本: Dim arr() As 型ReDim arr(行数-1, 列数-1)
  • Preserveの制約: 最後の次元しか伸縮できない。2次元なら「列数のみ」変更可能。

例題4:行は固定、列を追加していく(最後の次元を拡張)

Sub DynamicArray_2D_PreserveColumns()
    Dim scores() As Integer
    Dim rows As Long, cols As Long
    Dim r As Long, c As Long

    rows = 3      '3行(人)
    cols = 2      '2列(教科)

    ReDim scores(rows - 1, cols - 1)

    '初期データ
    scores(0, 0) = 80: scores(0, 1) = 70
    scores(1, 0) = 90: scores(1, 1) = 85
    scores(2, 0) = 75: scores(2, 1) = 88

    '列を1つ追加(教科を増やす)
    cols = cols + 1
    ReDim Preserve scores(rows - 1, cols - 1)

    '新しい列(c = 2)に値を入れる
    For r = 0 To rows - 1
        scores(r, 2) = 80 + r   '仮データ
    Next r

    '確認
    For r = 0 To rows - 1
        For c = 0 To cols - 1
            Debug.Print "r=" & r & ", c=" & c & " => " & scores(r, c)
        Next c
    Next r
End Sub
VB
  • コツ: 行を増やしたいケースは、新しい配列にコピーする方法が現実的(Preserve不可のため)。

シートとの連携で動的に読み書き

例題5:A列のデータ件数に合わせて配列を作る

Sub DynamicArray_FromSheet()
    Dim ws As Worksheet
    Dim lastRow As Long
    Dim arr() As Variant
    Dim i As Long

    Set ws = ThisWorkbook.Sheets("Data")

    '最終行を取得(A列にデータがある前提)
    lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row

    '件数に合わせて配列を確保
    ReDim arr(lastRow - 1) '0~lastRow-1

    'A1~A(lastRow) を読み込み
    For i = 1 To lastRow
        arr(i - 1) = ws.Cells(i, "A").Value
    Next i

    '80以上を出力(例)
    Dim outRow As Long: outRow = 1
    For i = 0 To UBound(arr)
        If IsNumeric(arr(i)) And arr(i) >= 80 Then
            ws.Cells(outRow, "C").Value = arr(i)
            outRow = outRow + 1
        End If
    Next i
End Sub
VB
  • 現場で便利: 件数に合わせてぴったりサイズにできる。無駄がない。

よくあるつまずきと対策

  • ReDimすると消える問題: 既存データを残したいなら必ず Preserve を付ける。
  • Preserveの制限: 2次元以上は「最後の次元だけ」拡張可能。行を増やすなら新配列へコピー。
  • 初期化の誤解: ReDimをすると要素は型の初期値にリセット(数値は0、文字列は空文字)。
  • 境界エラー: 添字の上限は UBound で必ず確認してからアクセス。
  • 効率問題: 何度も ReDim Preserve を繰り返すと遅い。まとめて増やすバッファ方式が有効。

練習問題(手を動かす)

  • 問題1: 不定件数の名前を入力ボックスで受け取り、「空入力が来たら終了」。受け取った名前を動的配列に蓄積して、最後に全部表示。
  • 問題2: 2次元の動的配列で「列を追加」しながら、テスト科目を増やしていく。各行の平均を都度計算して表示。
  • 問題3: A列の数値を読み込み、10件ごとにバッファ拡張しながら、合計と平均を計算してC1とC2に出力。

解答と解説

練習問題 1 不定件数の名前を入力して配列に蓄積する

Sub Practice1_CollectNames()
    Dim names() As String
    Dim inputName As String
    Dim count As Long

    count = 0
    '最初は要素0の配列を作らずに開始
    Do
        inputName = InputBox("名前を入力してください(空で終了)", "名前入力")
        If inputName = "" Then Exit Do

        '初回は配列を1要素で確保、以降は1つずつ増やす
        If count = 0 Then
            ReDim names(0)
            names(0) = inputName
            count = 1
        Else
            ReDim Preserve names(count) '既存データを保持しつつ1つ増やす
            names(count) = inputName
            count = count + 1
        End If
    Loop

    '結果表示
    If count = 0 Then
        MsgBox "名前は1つも入力されませんでした。"
    Else
        Dim i As Long, msg As String
        msg = "入力された名前一覧:" & vbCrLf
        For i = 0 To count - 1
            msg = msg & (i + 1) & ". " & names(i) & vbCrLf
        Next i
        MsgBox msg
    End If
End Sub
VB

解説

  • ポイントは ReDim と ReDim Preserve の使い分け。最初は配列が存在しないため最初の要素を作る処理が必要。
  • ReDim Preserve は中身を保持するが、頻繁に使うと処理が重くなる。件数が多くなる見込みならまとめて拡張する方法を検討する。
  • 配列は0スタートにしているので表示では見やすくするため (i + 1) を使っている。

練習問題 2 2次元動的配列で列を追加しながら科目を増やす

Sub Practice2_Dynamic2D_ExpandColumns()
    Dim scores() As Double
    Dim rows As Long, cols As Long
    Dim i As Long, j As Long
    Dim newScore As Double

    '行数(生徒数)を仮に決める
    rows = 3
    cols = 1 '最初は1科目だけ

    ReDim scores(rows - 1, cols - 1)

    '初期データ(例)
    scores(0, 0) = 80: scores(1, 0) = 75: scores(2, 0) = 90

    '科目を追加する例を繰り返し実行(ここでは2回追加)
    Dim addTimes As Long
    addTimes = 2
    For i = 1 To addTimes
        '列を1つ増やす(最後の次元なら Preserve で保持可能)
        cols = cols + 1
        ReDim Preserve scores(rows - 1, cols - 1)

        '新しい列に値を入れる(実際は入力Boxやシートから取得する)
        For j = 0 To rows - 1
            newScore = 70 + j + i '仮のデータ
            scores(j, cols - 1) = newScore
        Next j
    Next i

    '各行の平均を表示
    For i = 0 To rows - 1
        Dim s As Double
        s = 0
        For j = 0 To cols - 1
            s = s + scores(i, j)
        Next j
        Debug.Print "生徒" & (i + 1) & " 平均: " & Format(s / cols, "0.0")
    Next i
End Sub
VB

解説

  • 2次元配列は ReDim Preserve で「最後の次元(ここでは列数)」しか変更できない。行数を増やしたい場合は別配列に移すか、最初に十分な行数を確保しておく。
  • コード例では仮データを入れているが、実運用では InputBox やシートの値を読み込む。
  • 平均計算では常に現在の cols を使うこと。列の追加ごとに平均が変わる点に注意。

練習問題 3 A列を読み込みバッファ方式で拡張しつつ合計と平均を出す

Sub Practice3_BufferedReadAndStats()
    Dim ws As Worksheet
    Set ws = ThisWorkbook.Sheets("Data")

    Dim lastRow As Long
    lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
    If lastRow = 0 Then
        MsgBox "A列にデータがありません。"
        Exit Sub
    End If

    Dim buffer() As Double
    Dim capacity As Long
    Dim count As Long
    Dim i As Long

    '初期バッファサイズ
    capacity = 10
    ReDim buffer(capacity - 1)
    count = 0

    'A列を1行ずつ読み込んで必要なら拡張
    For i = 1 To lastRow
        If IsNumeric(ws.Cells(i, "A").Value) Then
            If count >= capacity Then
                '容量不足なら倍に拡張
                capacity = capacity * 2
                ReDim Preserve buffer(capacity - 1)
            End If
            buffer(count) = ws.Cells(i, "A").Value
            count = count + 1
        End If
    Next i

    '合計と平均を計算
    Dim total As Double
    total = 0
    For i = 0 To count - 1
        total = total + buffer(i)
    Next i

    Dim avg As Double
    If count > 0 Then
        avg = total / count
    Else
        avg = 0
    End If

    '結果をシートに出力(C1: 合計, C2: 平均)
    ws.Range("C1").Value = total
    ws.Range("C2").Value = avg

    MsgBox "件数: " & count & " 合計: " & total & " 平均: " & Format(avg, "0.00")
End Sub
VB

解説

  • バッファ方式は ReDim Preserve を繰り返す回数を減らして高速化するテクニック。ここでは初期容量10を用意し、満杯になったら倍にしている。
  • 読み込み時に IsNumeric で数値確認しているため、空白や文字列行を除外できる。
  • 最後に実データ数 count を使って合計・平均を計算している点が重要。配列の物理容量 capacity と実データ数 count を混同しない。

共通の注意点とコツ

  • ReDim だけだと中身は消える。既存データを残したいときは ReDim Preserve を使う。
  • Preserve は最後の次元しか拡張できない。2次元以上で別の次元を増やす場合は新配列にコピーする必要がある。
  • 頻繁に要素追加がある場合は1件ずつ拡張するよりもバッファ方式(まとめて拡張)を使うと速い。
  • 配列の上限は UBound を使って確認する。配列の実データ数は別変数で管理すると扱いやすい。
VBA
スポンサーリンク
シェアする
@lifehackerをフォローする
スポンサーリンク
タイトルとURLをコピーしました