【VBA】配列とDictionaryの使い分け完全ガイド|宣言・ReDim Preserveからキー重複エラー対策まで

VBAを覚えたての頃、僕はデータを溜め込む処理を全部配列で書いていた。

配列で困ることはほとんどなかったが、部署別の売上を集計するようなコードを書いたときに、様子が変わった。

配列の中を毎回ループで検索して「この部署、もう登録済みかどうか」を確認するコードになり、データ件数が増えるほど処理がもたつくようになった。

そこで知ったのがDictionary。キーで直接呼び出せるので、検索のためのループがまるごと不要になる。

この記事では、配列の宣言・初期化からDictionaryの使い方まで、「結局どっちを使えばいいのか」という判断軸を軸にまとめました。

配列とDictionary、何が違うのか

配列は、0番・1番・2番…というインデックス(連番)でアクセスする箱

一方Dictionaryは、”東京”や”田中”のようなキーでアクセスするラベル付きの引き出しと考えると分かりやすい。

両者の違いを表にすると、こうなる。

項目配列Dictionary
アクセス方法インデックス(0, 1, 2…)キー(文字列・数値)
要素の順序宣言順に保持される追加順に近いが仕様上は保証されない
キーの重複インデックスなので概念自体がないAddで重複させるとエラーになる
主な用途一時的なデータ保持、順序が意味を持つ処理集計、重複チェック、コード→名前の変換
事前準備不要参照設定 or CreateObjectの選択が必要

配列とDictionary、どっちを使うべきか

僕が実務で判断に使っている基準はシンプルで、「パフォーマンスを意識したい」「キーから直接呼び出したい」「可読性を上げたい」のいずれかに当てはまればDictionary

それ以外の、単純にデータを一時保存するだけの処理は配列で十分。

「パフォーマンスを意識したい」というのは、具体的には検索や重複チェックが絡む処理のこと。

配列であるキーが登録済みかを調べるには、先頭から順にループで比較するしかない(データがn件ならn回のループ)。

Dictionaryはハッシュテーブルという仕組みでキーを管理しているため、件数が増えてもほぼ一定の速さで検索できる。

件数が少ないうちは体感しにくいが、数千行を超えるデータを扱うマクロでは差がはっきり出てくる。

配列の基本を押さえる

固定長配列:サイズが決まっている処理に

固定長配列は、宣言時に要素数を指定するタイプ。サイズが変わらない分、シンプルで高速に動く。

VB
Sub DeclareFixedArray()
    Dim scores(0 To 4) As Integer ' 5つの要素を持つ整数型配列

    scores(0) = 80
    scores(1) = 90
    scores(2) = 85
End Sub

Dim 配列名(開始Index To 終了Index)で宣言する。特に指定しなければ0始まりになる。

動的配列:サイズが読めない処理に

データ件数が実行してみないと分からない場合は、動的配列を使う。

VB
Sub DeclareDynamicArray()
    Dim names() As String
    ReDim names(3) ' 要素数4(0〜3)

    names(0) = "Taro"
    names(1) = "Hanako"
End Sub

Dim 配列名()で型だけ宣言し、あとからReDimでサイズを決める流れ。

ReDim Preserveの罠

動的配列を覚えたての頃、一番ハマったのがここ。ReDimだけを使うと、それまでの配列の中身が消える。

ループの途中でうっかり素のReDimを書いてしまい、集計結果が全部消えて青ざめたことがある。

中身を保持したままサイズだけ変えたいときは、ReDim Preserveを使う。

VB
Sub ExtendArrayPreserve()
    Dim nums() As Long
    ReDim nums(2)
    nums(0) = 1
    nums(1) = 2

    ReDim Preserve nums(4) ' 既存の値を保持したまま要素数を拡張
    nums(3) = 3
    nums(4) = 4
End Sub

変更できるのは最終次元のみという制約もある。多次元配列でPreserveを使うときは要注意。

多次元配列:行×列のデータに

表形式のデータを扱いたいときは、多次元配列が便利。Excelのセルのようなイメージで管理できる。

VB
Sub Declare2DArray()
    Dim table(1 To 3, 1 To 2) As String

    table(1, 1) = "A"
    table(1, 2) = "B"
    table(2, 1) = "C"
End Sub

Dim 配列名(行の範囲, 列の範囲)で宣言する。行と列のインデックスをそれぞれ個別に指定できる。

配列の要素数を取得する

VBAには配列の要素数を直接返す関数がない。UBound(最大インデックス)とLBound(最小インデックス)の差分で算出する。

VB
Sub CountFixedArray()
    Dim fruits(1 To 5) As String
    Dim cnt As Long

    cnt = UBound(fruits) - LBound(fruits) + 1
    MsgBox "要素数は " & cnt & " です"
End Sub

+1を忘れがちなので要注意。インデックスの差だけだと1つ少なく数えてしまう。

また、動的配列はReDimでサイズを指定するまでは未初期化の状態。このままUBoundを使うとエラーになる点も覚えておきたい。

二次元配列では、第2引数に1(行方向)・2(列方向)を指定すれば、各次元の要素数を個別に取得できる。

VB
Sub Count2DArray()
    Dim matrix(1 To 3, 1 To 4) As Integer
    Dim rowCount As Long, colCount As Long

    rowCount = UBound(matrix, 1) - LBound(matrix, 1) + 1
    colCount = UBound(matrix, 2) - LBound(matrix, 2) + 1

    MsgBox "行数: " & rowCount & ", 列数: " & colCount
End Sub

Dictionaryを使う準備をする

Dictionaryは配列と違い、使い始める前にひと手間必要になる。方法は2つある。

早期バインディング(参照設定)

VBEの「ツール」→「参照設定」から「Microsoft Scripting Runtime」にチェックを入れる方法。

VB
Dim dict As New Dictionary ' 参照設定が前提
dict.Add "東京", 100

入力補完(インテリセンス)が効くのがメリット。ただし、このマクロを別のPCに配布すると、相手の環境で参照設定が外れていて動かないことがある。

実行時バインディング(CreateObject)

参照設定をせず、CreateObjectでDictionaryを生成する方法。

VB
Dim dict As Object
Set dict = CreateObject("Scripting.Dictionary")
dict.Add "東京", 100

参照設定なしで動くので、配布先のPC環境に左右されないのが強み。入力補完は効かなくなるが、実務で使うマクロは基本的にこちらにしている。

個人のツールとして手元だけで動かすなら早期バインディング、他の人に配るマクロなら実行時バインディング、というのが僕の使い分け。

Dictionaryの基本操作

基本のメソッドは7つ覚えておけば困らない。

VB
Sub DictionaryBasicMethods()
    Dim dict As Object
    Set dict = CreateObject("Scripting.Dictionary")

    dict.Add "東京", 100 ' キーと値を追加
    dict.Add "大阪", 80
    dict.Add "福岡", 50

    If dict.Exists("東京") Then ' キーの存在確認
        Debug.Print dict.Item("東京") ' 値の取得(Itemは省略可)
    End If

    Dim k As Variant
    For Each k In dict.Keys ' キー一覧の取得
        Debug.Print k & ": " & dict(k)
    Next k

    Debug.Print "件数: " & dict.Count ' 登録件数

    dict.Remove "福岡" ' 削除
End Sub
  • Add:キーと値を追加
  • Exists:キーが登録済みか確認
  • Item:値を取得・更新(dict("キー")と省略できる)
  • Keys:登録済みキーの一覧を配列で取得
  • Items:登録済み値の一覧を配列で取得
  • Count:登録件数を取得
  • Remove:指定したキーを削除

Dictionaryでよくあるエラーと対処

配列と同じくらい、僕が最初につまずいたのがキーの重複エラー。同じキーでAddを2回実行すると、実行時エラーになる。

VB
Sub DuplicateKeyError()
    Dim dict As Object
    Set dict = CreateObject("Scripting.Dictionary")

    dict.Add "東京", 100
    dict.Add "東京", 200 ' ← 実行時エラー(キーは既に存在します)
End Sub

集計処理では「同じキーが2回目に出てきたら加算する」という場面が頻繁にある。そのたびにExistsで確認してから、追加か更新かを分岐させるのが安全な書き方。

VB
Sub AvoidDuplicateKeyError()
    Dim dict As Object
    Set dict = CreateObject("Scripting.Dictionary")

    dict.Add "東京", 100

    If dict.Exists("東京") Then
        dict("東京") = dict("東京") + 200 ' 既存キーは加算して更新
    Else
        dict.Add "東京", 200 ' 新規キーは追加
    End If
End Sub

ちなみにItemへの代入(dict("東京") = 値)は、キーが存在しなければ自動的に追加、存在すれば上書きになる。

この挙動を知らずにAddだけで書き続けると、同じ重複エラーに何度も当たることになる。地味に見落としがちなポイント。

実務での使い分けを体感する:部署別集計

最後に、配列だと重くなりがちで、Dictionaryだと素直に書ける典型例を紹介する。

A列に部署名、B列に金額が並んだ表から、部署ごとの合計をD列・E列に書き出す処理。

VB
Sub SumByDepartment()
    Dim dict As Object
    Set dict = CreateObject("Scripting.Dictionary")

    Dim lastRow As Long
    lastRow = Cells(Rows.Count, 1).End(xlUp).Row

    Dim i As Long, dept As String, amount As Long
    For i = 2 To lastRow
        dept = Cells(i, 1).Value
        amount = Cells(i, 2).Value

        If dict.Exists(dept) Then
            dict(dept) = dict(dept) + amount
        Else
            dict.Add dept, amount
        End If
    Next i

    Dim k As Variant, r As Long
    r = 1
    For Each k In dict.Keys
        Cells(r, 4).Value = k
        Cells(r, 5).Value = dict(k)
        r = r + 1
    Next k
End Sub

これを配列だけで書こうとすると、「この部署は登録済みか」を確認するために、毎行ごとに配列全体をループで検索する処理が必要になる。

部署数が少なければ気にならないが、キーの候補が数百件を超えると、この検索コストが効いてくる。

dict.Exists(dept)の一行で済むDictionaryのほうが、コードの見通しも処理速度も良くなる場面。

よくある質問

本文で触れきれなかった細かい疑問をQ&Aでまとめておきます。

固定長配列はReDimでサイズ変更できますか?

できません。固定長配列(Dim arr(5) As Stringのように宣言したもの)に対してReDimを使うとコンパイルエラーになります。サイズ変更が必要な場合は、最初から動的配列(Dim arr() As String)として宣言してください。

Dictionaryのキーに数値は使えますか?

使えます。キーは文字列だけでなく、数値や日付などのデータ型を指定できます。ただし内部的には型を区別するため、キーとして1(数値)と"1"(文字列)は別のキーとして扱われる点には注意してください。

Dictionaryは配列より必ず速いのですか?

データ件数が少ない、または単純に先頭から順番に処理するだけの場合は、配列とDictionaryの速度差はほとんど体感できません。Dictionaryが優位になるのは、キーの存在確認や検索を繰り返す処理です。用途に合わない場面でDictionary化しても、コードが複雑になるだけのこともあります。

二次元配列のループはどう書けばいいですか?

行と列それぞれに対してLBound(arr, 次元)からUBound(arr, 次元)までのForループをネストさせます。1次元目が行、2次元目が列に対応するのが一般的な書き方です。

まとめ

配列は順序を持つ一時的なデータ保存に、Dictionaryはキーからの検索・集計・重複チェックに向いている。

迷ったら「パフォーマンス」「キー参照」「可読性」のどれかが当てはまるかを考えれば、自然とどちらを使うべきかが見えてくるはずだ。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

普段は主にPython開発をしています。
最近はAI駆動開発にも関わっています。

コメント

コメントする