VBA Dictionary(連想配列)の使い方|重複削除・高速集計を実務コードで解説

スポンサーリンク

「For EachもDo Loopも書けるようになったのに、データが数千行になると急に遅くなる……」

VBAをある程度書けるようになると、こんな壁にぶつかりますよね。特に「重複をチェックしながらループする」処理は、データが増えるほど一気に重くなります。Excelがフリーズして、保存もできずにヒヤッとした経験がある方も多いはずです。

この遅さを根本から解決してくれるのが VBA Dictionary(連想配列) です。この記事では、基本メソッドの使い方から「なぜ配列より速いのか」、そして重複削除・グループ集計・別シート転記の実務コードまで解説していきますね。

VBA Dictionary(連想配列)とは?

Dictionaryは、キー(Key)と値(Item)をペアで保管するオブジェクトです。連想配列やハッシュマップとも呼ばれます。

イメージは「ラベル付きの引き出し」です。引き出しにラベル(キー)を貼っておけば、中身(値)を探すときにラベルを見るだけで一発で取り出せます。これがDictionaryの本質です。

ふつうの配列が「1番目・2番目」と番号で管理するのに対して、Dictionaryは「商品コード」「部門名」のような意味のある名前で管理できます。

比較項目配列(Array)Dictionary
要素の指定方法番号(インデックス)キー(任意の文字列・数値)
キーの重複概念がない不可(一意)
「この値は既出か」の判定全要素を順に照合Existsで一発判定
主な用途連番管理、行データの一時保持重複削除、グループ集計、対応表

つまりDictionaryは「名前で出し入れできて、しかも検索が速い」入れ物だと覚えてください。この2つの特徴が、大量データ処理の遅さを解決してくれます。

NOTE

VBE(Visual Basic Editor)は Alt + F11 で起動します。リボンから開く場合は「開発」タブ →「Visual Basic」を選びます。「開発」タブが表示されていないときは、「ファイル」→「オプション」→「リボンのユーザー設定」で「開発」にチェックを入れてください。VBEが開いたら「挿入」→「標準モジュール」でコードを書く場所を作ります。画面構成がわからない方は、VBE画面の見方の記事も参考にしてみてください。

Dictionaryを使う準備(参照設定とCreateObject)

Dictionaryを使うには、最初にオブジェクトを生成する必要があります。生成方法は2通りあります。

方法1: 参照設定する(事前バインディング)

VBEのメニューから「ツール」→「参照設定」を開きます。一覧から Microsoft Scripting Runtime にチェックを入れます。これで準備完了です。

Sub UseEarlyBinding()
    Dim dic As Scripting.Dictionary '辞書オブジェクト
    Set dic = New Scripting.Dictionary

    dic.Add "りんご", 100
    MsgBox dic("りんご") & "円"
End Sub

参照設定すると、入力中に候補が出る入力補完(IntelliSense)が効きます。コードが書きやすくなるので、自分のPCで使う場合はこちらがおすすめです。

方法2: CreateObjectで生成する(実行時バインディング)

参照設定をせずに、コード内でオブジェクトを直接生成する方法です。

Sub UseLateBinding()
    Dim dic As Object '辞書オブジェクト
    Set dic = CreateObject("Scripting.Dictionary")

    dic.Add "りんご", 100
    MsgBox dic("りんご") & "円"
End Sub

CreateObjectは参照設定が不要です。そのため、ファイルを他の人に配布する場合に向いています。環境による設定の違いを気にしなくて済むからです。

TIP

迷ったら CreateObject を選んでおくと安全です。参照設定はファイルごとに必要で、配布先で外れていると動かなくなります。本記事のコードはすべてCreateObjectで統一しています。

Dictionaryの基本メソッド

Dictionaryの基本操作を1つずつ見ていきましょう。どれもシンプルなので、すぐに使えるようになりますよ。

Add:キーと値を追加する

Add はキーと値のペアを登録するメソッドです。

Dim dic As Object '辞書オブジェクト
Set dic = CreateObject("Scripting.Dictionary")

dic.Add "A001", "りんご" 'キー"A001"に値"りんご"を登録
dic.Add "A002", "みかん"

同じキーをもう一度Addするとエラーになります。キーは重複できないルールだからです。

Exists:キーがあるか調べる

Exists は、指定したキーが登録済みかどうかを True / False で返します。

If dic.Exists("A001") Then
    MsgBox "登録済みです"
Else
    MsgBox "未登録です"
End If

重複チェックの主役になるメソッドです。後ほどの実務コードで大活躍します。

Item:値を取り出す・書き換える

Item はキーに対応する値を取り出すプロパティです。既定プロパティなので、dic("キー") と短く書けます。

MsgBox dic("A001") '"りんご"が表示される

dic("A001") = "青りんご" '値を上書き

NOTE

Item には注意点があります。存在しないキーを dic("未登録キー") で参照すると、エラーにならず、そのキーが空の値で勝手に追加されてしまいます。存在確認には必ず Exists を使ってくださいItem での判定は誤動作のもとになります。

Keys・Items:全キー・全値を取り出す

Keys は全キーを、Items は全値を、それぞれ配列で返します。

Dim k As Variant '取り出すキー
For Each k In dic.Keys
    Debug.Print k & " → " & dic(k) 'キーと値をセットで出力
Next k

For Each でキーを1つずつ取り出し、dic(k) で対応する値を引いています。For Eachの使い方との組み合わせはDictionaryの定番パターンです。

Count・Remove:件数取得と削除

Count は登録ペア数を返すプロパティです。Remove は指定したキーのペアを削除します。

MsgBox "登録数: " & dic.Count & "件"

dic.Remove "A002" '"A002"のペアを削除
dic.RemoveAll '全ペアを削除

基本メソッド早見表

要素種別役割
Add(キー, 値)メソッドペアを追加(既存キーはエラー)
Exists(キー)メソッドキーの有無を True / False で返す
Item(キー) / dic(キー)プロパティ値を取得・上書き
Keysメソッド全キーを配列で返す
Itemsメソッド全値を配列で返す
Countプロパティ登録ペア数を返す
Remove(キー)メソッド指定キーのペアを削除
RemoveAllメソッド全ペアを削除

なぜDictionaryは配列より速いのか

ここがこの記事のいちばん大事なところです。「なぜ速いのか」を理解すると、Dictionaryを使うべき場面が自分で判断できるようになります。

配列の重複チェックは「総当たり」になる

配列で「この値はもう登場したか」を調べるには、先頭から順に1つずつ照合するしかありません。これを線形探索と呼びます。

たとえば1,000件のデータから重複を除こうとすると、1件ごとに既存リストを全部チェックします。結果として、最悪で約100万回(1,000×1,000)の照合が必要です。データが10,000件なら約1億回に膨れ上がります。

これが二重ループ(O(n^2))の正体です。件数が10倍になると、処理時間は100倍になります。

Dictionaryは「ラベルから直接たどる」

一方、Dictionaryは内部でハッシュテーブルという仕組みを使っています。キーを計算式(ハッシュ関数)にかけて、値を置く場所を直接割り出します。

引き出しのラベルを見て、いきなり目的の引き出しを開けるイメージです。他の引き出しを順番に開けて確認する必要がありません。

そのため、Existsdic(キー) での検索は、データ件数に関係なくほぼ一定時間(O(1))で終わります。重複チェックを伴うループ全体で見ても、DictionaryならO(n)で済みます。

件数が増えるほど差は爆発する

両者の照合回数を比べてみましょう。

データ件数配列の二重ループ(O(n^2))Dictionary(O(n))
100件約1万回100回
1,000件約100万回1,000回
10,000件約1億回1万回
100,000件約100億回10万回

100件程度なら配列でも一瞬です。しかし数千〜数万件になると、配列は数十秒〜数分かかる一方、Dictionaryは一瞬で終わります。この差が、冒頭の「急に遅くなる」問題の答えです。

TIP

配列が遅いのは「値で探す」ときだけです。番号(インデックス)で直接取り出す処理は配列も高速です。「名前や値で検索したい」ときがDictionaryの出番だと覚えておきましょう。

【実務】Dictionaryの活用コード3選

ここからは、実務でそのまま使えるパターンを3つ紹介します。どれもデータの準備から出力まで通しで動くコードです。

パターン1: 重複を削除して一意リストを作る

A列の商品コードから重複を除き、C列に一意のリストを書き出すコードです。

Sub RemoveDuplicates()
    Dim ws As Worksheet '対象シート
    Dim dic As Object '重複判定用の辞書
    Dim lastRow As Long '最終行
    Dim i As Long '行カウンター
    Dim key As String '判定するキー
    Dim outRow As Long '出力先の行

    Set ws = ActiveSheet
    Set dic = CreateObject("Scripting.Dictionary")
    lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
    outRow = 2

    '--- A列を1行ずつ読み、未登録のものだけC列に書き出す ---
    For i = 2 To lastRow
        key = ws.Cells(i, 1).Value
        If Not dic.Exists(key) Then
            dic.Add key, True '登録済みとして記録
            ws.Cells(outRow, 3).Value = key
            outRow = outRow + 1
        End If
    Next i

    MsgBox "重複を除いて " & dic.Count & " 件を書き出しました"
End Sub

ポイントは If Not dic.Exists(key) Then です。まだ登録されていないキーだけをC列に書き出しています。最終行は End(xlUp) で取得しています。詳しくは最終行を取得する方法をご覧ください。

パターン2: 部門別の売上を集計する

A列に部門名、B列に売上金額が入ったデータを、部門ごとに合計するコードです。

Sub GroupSum()
    Dim ws As Worksheet '対象シート
    Dim dic As Object '部門別集計用の辞書
    Dim lastRow As Long '最終行
    Dim i As Long '行カウンター
    Dim dept As String '部門名(キー)
    Dim amount As Double '売上金額
    Dim k As Variant '出力時のキー
    Dim outRow As Long '出力先の行

    Set ws = ActiveSheet
    Set dic = CreateObject("Scripting.Dictionary")
    lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row

    '--- 部門名をキーにして売上を加算していく ---
    For i = 2 To lastRow
        dept = ws.Cells(i, 1).Value
        amount = ws.Cells(i, 2).Value
        If dic.Exists(dept) Then
            dic(dept) = dic(dept) + amount '既存部門に加算
        Else
            dic.Add dept, amount '新しい部門を登録
        End If
    Next i

    '--- 集計結果をD・E列に書き出す ---
    outRow = 2
    For Each k In dic.Keys
        ws.Cells(outRow, 4).Value = k
        ws.Cells(outRow, 5).Value = dic(k)
        outRow = outRow + 1
    Next k

    MsgBox dic.Count & " 部門を集計しました"
End Sub

dic(dept) = dic(dept) + amount が集計の心臓部です。既に登録済みの部門なら現在の合計に加算し、初登場の部門なら新規登録しています。ピボットテーブルのグループ集計をコードで実現するイメージです。条件分岐はIf文の使い方で行っています。

パターン3: コード→名前の対応表で別シートに転記する

「商品マスタ」シートの対応表をDictionaryに読み込み、「売上」シートの商品コードを商品名に変換しながら転記するコードです。VLOOKUPをメモリ上で高速実行するイメージです。

Sub MappingTransfer()
    Dim wsM As Worksheet '商品マスタシート
    Dim wsS As Worksheet '売上シート
    Dim dic As Object '対応表の辞書
    Dim lastRowM As Long 'マスタの最終行
    Dim lastRowS As Long '売上の最終行
    Dim i As Long '行カウンター
    Dim code As String '商品コード(キー)

    Set wsM = Worksheets("商品マスタ")
    Set wsS = Worksheets("売上")
    Set dic = CreateObject("Scripting.Dictionary")

    '--- マスタのコードと名前を対応表として読み込む ---
    lastRowM = wsM.Cells(wsM.Rows.Count, 1).End(xlUp).Row
    For i = 2 To lastRowM
        dic(wsM.Cells(i, 1).Value) = wsM.Cells(i, 2).Value
    Next i

    '--- 売上シートのコードを名前に変換してC列に書き出す ---
    lastRowS = wsS.Cells(wsS.Rows.Count, 1).End(xlUp).Row
    For i = 2 To lastRowS
        code = wsS.Cells(i, 1).Value
        If dic.Exists(code) Then
            wsS.Cells(i, 3).Value = dic(code) '対応する名前を転記
        Else
            wsS.Cells(i, 3).Value = "該当なし"
        End If
    Next i

    MsgBox "転記が完了しました"
End Sub

対応表を一度Dictionaryに読み込んでしまえば、あとは dic(code) で一発参照できます。売上データが何万行あっても高速です。VLOOKUPの数式を大量に貼るより軽く、確実に動きます。

NOTE

マスタの読み込みで dic(キー) = 値 を使っているのは、マスタ側に重複コードがあっても後の行で上書きされるだけでエラーにならないからです。Add だと重複コードでエラーになるので、対応表の読み込みではこの書き方が安全です。

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

エラー原因対処法
実行時エラー ‘457’:キーが重複同じキーを Add したExists で事前チェックする。または dic(キー) = 値 で上書きする
「ユーザー定義型は定義されていません」参照設定なしで As Scripting.Dictionary と書いた参照設定を追加する。または CreateObjectAs Object に変える
実行時エラー ‘5’:引数が不正存在しないキーを Remove したRemove の前に Exists でキーの有無を確認する
意図しない空キーが増える存在しないキーを Item で参照した存在確認は必ず Exists で行う。Item で判定しない
実行時エラー ‘429’:オブジェクトを作成できませんまれに環境にコンポーネントがないWindowsには標準搭載。エラー時はOSやOfficeの状態を確認する

NOTE

Dictionaryを使うマクロは、必ず マクロ有効ブック(.xlsm) で保存してください。通常の .xlsx で保存すると、マクロが消えてしまいます。

配列・コレクションとの使い分け

VBAでデータをまとめて扱う方法には、配列・Collection・Dictionaryの3つがあります。場面に合わせて選びましょう。

比較項目配列(Array)CollectionDictionary
参照設定不要不要CreateObjectなら不要
キーでの管理できない追加時のみできる
キーの存在確認自前で実装メソッドなしExists
キー一覧の取得できないできないKeys
値で探す速さ遅い(線形探索)遅い速い(ハッシュ)
主な用途連番・行データの保持単純な要素の束重複削除・集計・対応表

使い分けの目安

  • 番号で順番に処理する → 配列
  • 単純に要素を束ねたいだけ → Collection
  • キーで検索・重複判定・集計したい → Dictionary

TIP

「重複を除きたい」「○○ごとに合計したい」「コードから名前を引きたい」という処理が出てきたら、Dictionaryを思い出してください。二重ループで書きかけていたコードが、半分以下の行数で、しかも何倍も速くなります。

まとめ

VBA Dictionaryは「キーと値をペアで保管し、高速に出し入れできる」オブジェクトです。

  • Add でペアを追加し、Exists でキーの有無を判定する
  • 値の取得・上書きは dic(キー) で書ける(存在しないキーの参照は厳禁)
  • Keys / Items で全要素を取り出し、For Each でループする
  • 速い理由はハッシュテーブル。値で探す処理が配列より圧倒的に速い
  • 重複削除・グループ集計・別シート転記の3パターンが実務の定番

データが数千行を超えて「急に遅くなった」と感じたら、Dictionaryの出番です。まずは重複削除のコードをVBEに貼り付けて、二重ループとの速度差を体感してみてくださいね。

Dictionaryを使いこなせたら、次はVBA学習ロードマップで全体の学習ステップを確認してみましょう。

関連記事

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