目次
はじめに
前回の記事では、辞書(Dictionary)に配列を格納する際の基本的な処理を紹介しました。しかし、実務では「配列の0番目は商品コードで、1番目以降だけを集計したい」といった状況があります。この記事では、配列の一部だけを更新できる、より汎用的な関数を紹介します。
配列の一部だけを更新したい場面
実務でデータを扱う際、配列に数値以外の情報が含まれることがよくあります。
【よくあるパターン】
' 配列の構造例
' arr(0) = "A001" ' 商品コード(固定値)
' arr(1) = 100 ' 1月の売上(加算対象)
' arr(2) = 200 ' 2月の売上(加算対象)
' arr(3) = 150 ' 3月の売上(加算対象)
このような配列では、0番目の商品コードは固定値なので加算する必要がなく、1番目から3番目だけを集計対象にしたいケースがあります。
【範囲指定が必要な理由】
範囲指定ができる関数を作ることで、以下のような利点があります。
- 配列内の特定の範囲だけを集計対象にできる
- ヘッダーやフッターなど非数値データを除外できる
- 同じ関数を様々な配列構造に対応させられる
配列の構造は扱うデータによって様々です。範囲を指定できる関数を作っておくことで、データ構造が変わっても柔軟に対応できます。
範囲指定ができる関数の設計
配列の更新範囲を指定できる関数を作成します。引数として開始インデックスと終了インデックスを受け取ることで、柔軟な処理が可能になります。
' 辞書に対して新規登録または更新を行う関数(範囲指定版)
' 引数: 辞書、キー、配列、加算開始・終了インデックス
Public Sub UpdateDict(ByRef dict As Scripting.Dictionary, _
ByVal key As String, ByVal arr As Variant, _
ByVal startIdx As Long, ByVal endIdx As Long)
' キー存在確認
If Not dict.Exists(key) Then
' 新規登録
dict.Add key, arr
Else
' 既存 -> 指定範囲の値を加算
Dim i As Long
Dim tmp As Variant: tmp = dict(key)
For i = startIdx To endIdx
tmp(i) = tmp(i) + arr(i)
Next i
dict(key) = tmp
End If
End Sub
この関数では、startIdxとendIdxで加算する範囲を明示的に指定できます。例えば、配列が(0 To 3)で0番目を除外したい場合は、startIdx = 1、endIdx = 3と指定します。
【関数の特徴】
- 新規登録の場合は配列全体をそのまま登録する
- 更新の場合は指定範囲だけを加算する
- 範囲外の要素は変更されない
新規登録時は配列全体を登録しますが、更新時は指定範囲だけを加算します。これにより、商品コードなどの固定値を保持しながら、数値データだけを集計できます。
実際に試してみる
実際に動作を確認できるコードを用意しました。空白のExcelブックを開き、以下の手順で試してみてください。
- 新しいExcelブックを開く
-
Alt + F11でVBエディタを開く - 「ツール」→「参照設定」から「Microsoft Scripting Runtime」にチェックを入れる
- 「挿入」→「標準モジュール」で新しいモジュールを追加
- 以下のコードを貼り付けて実行
辞書を使うには参照設定が必要です。設定方法は以下の記事で解説しています。
【基本例】商品コードを含む配列の集計
Sub TestPartialUpdate()
' 辞書を作成
Dim dict As New Scripting.Dictionary
Dim data(0 To 3) As Variant
' 商品Aのデータ(0番目は商品コード)
data(0) = "A001"
data(1) = 100: data(2) = 200: data(3) = 150
UpdateDict dict, "商品A", data, 1, 3
' 商品Aの追加データ
data(0) = "A001"
data(1) = 50: data(2) = 80: data(3) = 30
UpdateDict dict, "商品A", data, 1, 3
' 結果を表示
Dim result As Variant
result = dict("商品A")
Dim msg As String
msg = "商品Aの集計結果" & vbCrLf & vbCrLf
msg = msg & "商品コード: " & result(0) & vbCrLf
msg = msg & "1月: " & result(1) & "円" & vbCrLf
msg = msg & "2月: " & result(2) & "円" & vbCrLf
msg = msg & "3月: " & result(3) & "円"
MsgBox msg, vbInformation
End Sub
' 辞書に対して新規登録または更新を行う関数(範囲指定版)
Public Sub UpdateDict(ByRef dict As Scripting.Dictionary, _
ByVal key As String, ByVal arr As Variant, _
ByVal startIdx As Long, ByVal endIdx As Long)
If Not dict.Exists(key) Then
dict.Add key, arr
Else
Dim i As Long
Dim tmp As Variant: tmp = dict(key)
For i = startIdx To endIdx
tmp(i) = tmp(i) + arr(i)
Next i
dict(key) = tmp
End If
End Sub
実行すると、以下の結果が表示されます。
商品Aの集計結果
商品コード: A001
1月: 150円
2月: 280円
3月: 180円
商品コード(0番目)は変更されず、1〜3番目だけが加算されていることが確認できます。
【応用例】複数の商品データを集計してシートに出力
より実用的な例として、商品コードを含むデータを集計してシートに出力するコードです。
Sub AggregateProductSalesWithCode()
' シートをクリア
Cells.Clear
' 辞書を作成
Dim dict As New Scripting.Dictionary
Dim data(0 To 3) As Variant
' ====== データ1回目の登録 ======
' 商品Aのデータ
data(0) = "A001"
data(1) = 100: data(2) = 200: data(3) = 150
UpdateDict dict, "商品A", data, 1, 3
' 商品Bのデータ
data(0) = "B002"
data(1) = 80: data(2) = 120: data(3) = 90
UpdateDict dict, "商品B", data, 1, 3
' ====== データ2回目の登録(加算される) ======
' 商品Aの追加データ
data(0) = "A001"
data(1) = 50: data(2) = 80: data(3) = 30
UpdateDict dict, "商品A", data, 1, 3
' 商品Cのデータ(新規)
data(0) = "C003"
data(1) = 200: data(2) = 150: data(3) = 180
UpdateDict dict, "商品C", data, 1, 3
' 商品Bの追加データ
data(0) = "B002"
data(1) = 40: data(2) = 60: data(3) = 50
UpdateDict dict, "商品B", data, 1, 3
' ====== 結果をシートに出力 ======
' 見出し行
Range("A1").Value = "商品名"
Range("B1").Value = "商品コード"
Range("C1").Value = "1月"
Range("D1").Value = "2月"
Range("E1").Value = "3月"
Range("F1").Value = "合計"
' データ行
Dim key As Variant
Dim row As Long: row = 2
For Each key In dict.Keys
Dim arr As Variant
arr = dict(key)
Cells(row, 1).Value = key
Cells(row, 2).Value = arr(0) ' 商品コード
Cells(row, 3).Value = arr(1) ' 1月
Cells(row, 4).Value = arr(2) ' 2月
Cells(row, 5).Value = arr(3) ' 3月
Cells(row, 6).Formula = "=SUM(C" & row & ":E" & row & ")"
row = row + 1
Next key
' 列幅を自動調整
Columns("A:F").AutoFit
MsgBox "売上集計が完了しました", vbInformation
End Sub
' 辞書に対して新規登録または更新を行う関数(範囲指定版)
Public Sub UpdateDict(ByRef dict As Scripting.Dictionary, _
ByVal key As String, ByVal arr As Variant, _
ByVal startIdx As Long, ByVal endIdx As Long)
If Not dict.Exists(key) Then
dict.Add key, arr
Else
Dim i As Long
Dim tmp As Variant: tmp = dict(key)
For i = startIdx To endIdx
tmp(i) = tmp(i) + arr(i)
Next i
dict(key) = tmp
End If
End Sub
このコードを実行すると、自動的に集計が行われ、以下のような表がシートに出力されます。
| 商品名 | 商品コード | 1月 | 2月 | 3月 | 合計 |
|---|---|---|---|---|---|
| 商品A | A001 | 150 | 280 | 180 | 610 |
| 商品B | B002 | 120 | 180 | 140 | 440 |
| 商品C | C003 | 200 | 150 | 180 | 530 |
商品コードは変更されず保持され、売上データ(1〜3番目)だけが正しく加算されていることが確認できます。
UpdateDict dict, "商品A", data, 1, 3と呼び出すことで、配列の1番目から3番目だけが加算対象になります。0番目の商品コードは初回登録時の値が保持されます。
【別パターン】配列の後半部分だけを集計する
範囲指定を変えることで、様々なデータ構造に対応できます。例えば、配列の前半にヘッダー情報、後半に数値データがある場合です。
Sub TestDifferentRange()
Dim dict As New Scripting.Dictionary
Dim data(0 To 5) As Variant
' 配列の構造
' 0-1番目: ヘッダー情報(固定)
' 2-5番目: 数値データ(加算対象)
data(0) = "商品A"
data(1) = "カテゴリ1"
data(2) = 100: data(3) = 200: data(4) = 150: data(5) = 120
UpdateDict dict, "商品A", data, 2, 5
data(0) = "商品A"
data(1) = "カテゴリ1"
data(2) = 50: data(3) = 80: data(4) = 30: data(5) = 60
UpdateDict dict, "商品A", data, 2, 5
' 結果確認
Dim result As Variant: result = dict("商品A")
Debug.Print result(0), result(1) ' 商品A, カテゴリ1
Debug.Print result(2), result(3), result(4), result(5) ' 150, 280, 180, 180
End Sub
Public Sub UpdateDict(ByRef dict As Scripting.Dictionary, _
ByVal key As String, ByVal arr As Variant, _
ByVal startIdx As Long, ByVal endIdx As Long)
If Not dict.Exists(key) Then
dict.Add key, arr
Else
Dim i As Long
Dim tmp As Variant: tmp = dict(key)
For i = startIdx To endIdx
tmp(i) = tmp(i) + arr(i)
Next i
dict(key) = tmp
End If
End Sub
このように、startIdxとendIdxを変えるだけで、様々な配列構造に対応できます。
まとめ
配列の更新範囲を指定できるUpdateDict関数を作成することで、商品コードなどの固定値を含むデータも柔軟に扱えます。範囲指定により、配列内の特定部分だけを集計対象にでき、様々なデータ構造に対応可能です。実務では、データの構造に応じて適切な範囲を指定することで、効率的な集計処理が実現できます。