【入門編】【上級プロ】CollectionオブジェクトとDictionaryを用いた、タスク情報のメモリ内高速検索アルゴリズム – Project VBA解析バイブル

スポンサーリンク

こんにちは!Microsoft ProjectのVBA(Project VBA)の世界へようこそ。
マクロの記録から一歩踏み出し、「もっと実務で使える、速くてスマートなコードを書きたい!」と思っているあなたへ。

今回は、数千件規模の巨大なスケジュールを扱うプロの現場で必須となる「CollectionオブジェクトとDictionaryを用いた、タスク情報のメモリ内高速検索アルゴリズム」を徹底解説します。

「タスクを探すのに毎回ループを回していて、処理が重くてイライラする…」
そんな悩みを抱えているなら、ここをクリアすればあなたのVBAスキルは間違いなく一段上のステージに到達します。さあ、一緒に本質を学んでいきましょう!

1. なぜ「毎回ループ」は遅いのか?(Project VBAの裏側)

MS Projectでスケジュールを管理していると、数千行、数万行のタスクを扱うことは珍しくありません。例えば、「特定のWBSコードやタスク名を持つタスクを探して、担当者やコストを変更したい」という処理を考えてみましょう。

初心者がやりがちなのが、この書き方です。

‘ 【アンチパターン】毎回全タスクを頭から探し直す愚行
Dim t As Task
For Each t In ActiveProject.Tasks
If Not t Is Nothing Then
If t.WBS = “1.2.3” Then
‘ 処理を行う
t.Text1 = “更新完了”
Exit For
End If
End If
Next t

一見、問題な culturales に見えますが、これがタスク数 × 検索回数分だけ繰り返されるとどうなるでしょう?
数千件のタスクに対してこれを何十回も実行すると、VBAは途端に重くなり、画面が固まったような状態(フリーズ)に陥ります。

なぜ遅いのか?

MS Projectのオブジェクトモデル(`ActiveProject.Tasks`)は、VBAからアクセスするたびに内部のCOMブリッジを通過してProject本体へデータを問い合わせに行っています。 この「往復コスト」が、巨額のパフォーマンスロスを生んでいるのです。

2. 解決策:メモリ上に「索引(インデックス)」を作る

ここで登場するのが、Dictionary(またはCollection)です。
考え方は、本の後ろにある「索引(インデックスページ)」と同じです。

1. 最初に一度だけ全タスクをメモリ上に読み込み、`Key`(例: WBSやタスクID)と `Item`(タスクオブジェクトそのもの)のペアとして格納します。
2. 2回目以降の検索は、Project本体に問い合わせず、一瞬でメモリ(RAM)上からお目当てのタスクを引き当てます。

図解するとこういうイメージです。

[初回だけ実行:全件スキャン]
ActiveProject.Tasks ──(ドミノ式に取得)──> 【Dictionary (メモリ内)】
├ Key: “1.0” => Taskオブジェクト(A)
├ Key: “1.1” => Taskオブジェクト(B)
└ Key: “1.2” => Taskオブジェクト(C)

[2回目以降の検索:一瞬でヒット!]
Dictionary(“1.1”) ──> 一発アクセス!(ループ不要・爆速)

これによって、O(N) だった検索コストを O(1) にまで圧縮できます。体感速度は文字通り「桁違い」になりますよ。

3. 実装コード:Dictionaryを用いた高速検索エンジン

それでは、実際に現場で使える実用的なコードを見ていきましょう。
ここでは、VBA標準の機能を超えて強力なキー・バリュー操作ができる `Scripting.Dictionary` を使用します。

> ※注意:Dictionaryを使うには、VBEのメニューから「ツール」>「参照設定」で 「Microsoft Scripting Runtime」 にチェックを入れておくか、後述する遅延バインディングを使用します。今回は安全のため、参照設定不要の遅延バインディング(CreateObject)で記述しています。

Option Explicit

Sub HighSpeedTaskSearchSample()
Dim dictTasks As Object
Set dictTasks = CreateObject(“Scripting.Dictionary”)

Dim t As Task
Dim startTime As Double
startTime = Timer ‘ パフォーマンス計測用

‘ ==========================================
‘ Step 1: メモリ上にインデックス(辞書)を作成する
‘ ==========================================
‘ ※数千件のループは「最初の一回」だけに抑える!
For Each t In ActiveProject.Tasks
If Not t Is Nothing Then
‘ キーとして一意になる情報(ここではWBSコードを使用)を登録
‘ すでに同じキーが存在しないかチェックするとより安全です
If Not dictTasks.Exists(t.WBS) Then
Set dictTasks(t.WBS) = t
End If
End If
Next t

Debug.Print “インデックス構築完了: ” & Format(Timer – startTime, “0.00秒”)

‘ ==========================================
‘ Step 2: メモリ上から爆速でタスクを検索・操作する
‘ ==========================================
Dim searchWBS As String
searchWBS = “1.2.1” ‘ 探したいWBSコード

If dictTasks.Exists(searchWBS) Then
‘ ループなしで一発取得!
Dim targetTask As Task
Set targetTask = dictTasks(searchWBS)

‘ プロパティの読み書き
MsgBox “タスクを発見しました!” & vbCrLf & _
“名称: ” & targetTask.Name & vbCrLf & _
“期間: ” & targetTask.Duration / 480 & “日”, vbInformation, “高速検索成功”

‘ 例:値を書き換える
targetTask.Text1 = “自動処理済み”

Else
MsgBox “指定されたWBSコード ” & searchWBS & ” は存在しません。”, vbExclamation
End If

‘ 後片付け
Set dictTasks = Nothing
End Sub

4. コードの深掘りとポイント解説

ここで、コードの重要なポイントを先輩エンジニアの視点で解説します。

① `CreateObject(“Scripting.Dictionary”)` のメリット

参照設定を行わずにDictionaryを生成するこの手法(遅延バインディング)を使えば、あなたが書いたマクロを他のメンバーのPC(ExcelやProjectが入った環境)に配った際も、「参照設定が外れてコンパイルエラーになる」というトラブルを防げます。配布用のマクロの鉄則です。

② `Set targetTask = dictTasks(searchWBS)` の作法

オブジェクトをDictionaryに格納する際、および取り出す際は、必ず `Set` キーワードを使用してください。これを忘れると「オブジェクトが必要です」というお馴染みの実行時エラーに悩まされることになります。

③ Collectionオブジェクトとの使い分け

VBA標準の `Collection` オブジェクトでも似たようなことはできますが、Collectionには「すでに同じキーが登録されているか判定する機能(Existsメソッド)がない」という致命的な弱点があります。
そのため、キーの重複エラーを避けるために `On Error Resume Next` などの泥臭いエラーハンドリングを書く必要があり、コードが汚れます。特別な理由がない限り、キー検索には `Scripting.Dictionary` を選ぶのがプロの選択です。

5. 陥りやすい罠とエラー回避の知見

最後に、実務でこのアルゴリズムを導入した際に陥りがちな罠をシェアします。

  • 罠1: 削除されたタスク(空のタスク)へのアクセス
  • `For Each t In ActiveProject.Tasks` を回すとき、途中に削除されたタスクが含まれていると `t` が `Nothing` になります。必ず `If Not t Is Nothing Then` でガードしましょう。
  • 罠2: キーの大文字・小文字の区別
  • `Scripting.Dictionary` は、デフォルトではキーの大文字・小文字を区別します(”Task-1″ と “task-1” は別物扱い)。もしWBSやIDの大文字小文字揺れが心配な場合は、生成後に `dictTasks.CompareMode = TextCompare` を設定すると、大文字小文字を区別しない優しい辞書になります。

まとめ:ここをクリアすれば、Project VBAは怖くない!

今回は、CollectionとDictionaryを用いたメモリ内高速検索アルゴリズムについて解説しました。

  • 毎回Project本体に問い合わせる「全件ループ」は悪である
  • 最初に1回だけDictionaryに詰めて、インデックス(索引)を作る
  • 2回目以降の検索はメモリ経由で爆速になる

このアーキテクチャを自分の引き出しに持っているだけで、大規模なスケジュールデータを扱うマクロのパフォーマンスは劇的に向上します。ぜひ、あなたの実務のコードに取り入れて、周囲を驚かせてやりましょう!

それでは、次のステップでもっとスマートな自動化の世界を一緒に楽しみましょう。お疲れ様でした!

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