【テクニカル・上級編】コレクション(Collection)と辞書(Dictionary)の使い分け:データ検索を爆速にする – Excel VBA解析バイブル

スポンサーリンク

探索のコストを知る者へ:CollectionとDictionaryの境界線

Excel VBAで数万行のデータを扱う際、初心者は決まって`For Each`によるネストループを書き、計算時間の増大に絶望する。業務システムにおいて「計算量は正義」だ。O(n²)の迷路を彷徨うか、O(1)の直通エレベーターに乗るか。その分かれ道が、`Collection`と`Scripting.Dictionary`の使い分けにある。

本稿では、シニアエンジニアが知るべき、メモリの深淵と実行速度の極致について語る。

—

1. なぜCollectionでは不十分なのか

`Collection`はVBA標準の集合体だが、その実態は単純な連結リスト(Linked List)だ。インデックスによるアクセスや要素の追加には長けているが、特定のキーを用いた「検索」においては、全要素を順番に走査するO(n)のコストを支払うことになる。

一方、`Scripting.Dictionary`はハッシュテーブル(Hash Table)だ。キーをハッシュ関数で数値化し、メモリ上の特定領域に直接アクセスする。これにより、データ量が増大しても検索速度はほぼ一定(O(1))を保つ。「検索」が絡む業務ロジックで`Collection`を選択する理由は存在しない。

—

2. 爆速Dictionary構築の極意

`Scripting.Dictionary`をただ使うだけではプロとは呼べない。メモリの断片化を防ぎ、Windowsのメモリ管理を味方につける実装が必須だ。

高速検索の実装例

Option Explicit

‘ 参照設定不要で利用可能な「遅延バインディング」を採用(環境依存を排除)
‘ 膨大なデータセットを扱うための高速化手法
Public Sub OptimizeDictionarySearch()
Dim dict As Object
Dim rngData As Variant
Dim i As Long

‘ メモリの連続領域を確保するために、Rangeを直接ループせず配列に格納する
‘ セルへのアクセスはVBAにおいて最も低速なI/O操作である
rngData = Sheets(“Data”).Range(“A1:B100000”).Value

Set dict = CreateObject(“Scripting.Dictionary”)

‘ 辞書登録:キーの重複チェックをDictionaryに任せる(IF文の分岐を減らす)
For i = 1 To UBound(rngData, 1)
If Not dict.Exists(rngData(i, 1)) Then
dict.Add rngData(i, 1), rngData(i, 2)
End If
Next i

‘ 検索:O(1)の世界へ
Debug.Print “検索結果: ” & dict(“TargetKey”)

‘ 明示的解放:VBAのガーベジコレクションを待つな
Set dict = Nothing
End Sub

—

3. レガシー環境とメモリ管理の深淵

大規模なシステムを構築する場合、VBAのメモリ解放は死活問題だ。`Scripting.Dictionary`は`COM`オブジェクトであるため、スコープを抜けても即座に物理メモリが解放されるとは限らない。

シニアが守るべき3つの鉄則

1. オブジェクトは必ずNothingで潰す: 特にループ内で`CreateObject`を行うような設計は、メモリリークの温床だ。必ずループ外でインスタンス化し、使い回せ。
2. Variant型の過信を捨てる: 配列に格納する際、型を明示的に変換(CStr, CLng等)することで、ハッシュ計算時の型不一致によるオーバーヘッドを回避できる。
3. APIによる限界突破: もし数百万行のデータを扱い、Dictionaryのメモリ消費が許容できない場合は、`Kernel32.dll`の`GlobalAlloc`等を用いたメモリマッピングを検討すべきだ。しかし、まずはDictionaryを使い倒すことが先決である。

—

4. アーキテクトの視点:システム間連携を見据えて

Excelを単なる集計ツールではなく「フロントエンド」と捉えるなら、DictionaryはJSONデータとの仲介役となる。`Dictionary`を再帰的に定義すれば、複雑なネスト構造を持つAPIレスポンスのパースも容易だ。

‘ JSON構造をDictionaryでシミュレートする例
Public Function CreateNestedDict() As Object
Dim root As Object
Set root = CreateObject(“Scripting.Dictionary”)

Dim subDict As Object
Set subDict = CreateObject(“Scripting.Dictionary”)

subDict.Add “Status”, “Success”
root.Add “Response”, subDict

Set CreateNestedDict = root
End Function

—

結論:コードに魂を込めろ

「動けばいい」というコードは、数年後の自分自身を苦しめる負債になる。
`Scripting.Dictionary`は単なるデータ構造ではない。それは、計算資源という限られたリソースを最大限に活かすための、エンジニアとしての矜持だ。

次にVBAを書くとき、`For`ループの中に`If`で検索を書こうとしたら、その手を止めよ。その瞬間、あなたはO(1)の効率を選択できる。それが、伝説への第一歩だ。

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