[VBA] Dictionaryの使い方。簡単なpivotを作る。

この人はVBAがどのくらい出来るのかなって思った時にする質問で
簡単に聞けて、簡単に答えられるのは
「Dictionaryって使ってますか？」ではないでしょうか。
使っているか、使ってないか、なんかよくわからない説明をするか
反応によってその人のレベルがよくわかるようです。

私は「知っているけど使っていない」というなんかよくわからない説明をする派でした。
QiitaでこのDictionaryを使っているサンプルがあって、
私のレベルは「詳しく知らないから使っていない」だったことを知り、Dictionaryについて調べてみましたのでまとめてみます。


## 簡単にDictionaryについて説明
英語のDictionaryを訳すと「辞書」ですが、他のプログラム言語でいうと、連想配列やHashと呼ばれるものです。辞書は忘れていいと思います。
Hashと同様にkeyとvalueを一つのセットとして、複数セット持つことができます。(VBAではvalueではなくitemと言います。ですが、以下valueで統一します)
keyに重複は許されていません。後述しますが、重複されたデータを格納しようとすると、エラーになるか、valueが上書きされます。
このあたりも他の言語のものと同じだと思います。

## 使い方
### 宣言
変数のデータ型をvariantかobjectにする。
CreateObject関数を使う。

```vb
  Dim dic As Variant
  Set dic = CreateObject("scripting.dictionary")
```

配布等を考えるとこのやり方がオススメです。
他の方法は、参照設定をし、dimでnewを使うか、newでインスタンスを作ってください。
私は参照設定方式を使っていませんのでサンプルなしで。

### 格納
Collectionと違い、keyは数値でも文字列でも大丈夫です。

```vb
dic(key) = value
' dic(key)はdic.item(key)の省略版です。
```

このやり方ならkeyの重複に関しては上書きをしていきます。
上書きが嫌なら `dic.exists(key)` がkeyの存在有無をbool型で返してくれるので、これを使って格納処理を制御するのがいいと思います。

```vb
dic.Add key, value
```

このやり方だと、すでにkeyが存在する場合エラーが発生します。
基本的に私はaddメソッドを使っていません。
余談ですが、Collectionのaddとはkeyとvalueの並びが逆のようです。

### 値を呼び出す
`dic(key)`でvalueを返します。
keyが分かっていて、一つだけならこのやり方ですね。

#### .keysと.itemsで配列を取得

```vb
Dim b As Variant
Dim a As Variant
a = dic.keys
b = dic.items
```

aにはdicのkeyたちが配列になって入ります。
bにはdicのitemたちが配列になって入ります。

### forで回す
VBAでは`for`と`for each`がありますが、両方とも使えます。

#### for next
上記の配列を踏まえてforを使います。

```vb
Dim i As Long
Dim a As Variant
a = dic.keys
For i = LBound(a) To UBound(a)
  Debug.Print a(i), dic(a(i))
Next i
```

私の場合､このやり方をすると頭が混乱してきます｡｡
aはkeyたちが入っている配列です｡
その配列の要素数を使ってforを回します｡
iの数値を使ってaのkeyをひとつずつ取り出し､そのkeyを使って元のdicに入っているvalueを参照します｡

#### for each next
VBAの場合、「keyとvalueのセット」を回すことができません。inの後にdic.keysかdic.itemsを指定します。

```vb
Dim b As Variant
For Each b In dic.keys
  Debug.Print b, dic(b)
Next b
' bにはkeyが入ります。
' このbを使ってvalueを呼び出すことが可能です。

For Each b In dic.items
  Debug.Print b
Next b
' bにはvalueが入ります。
' この場合はkeyを取り出すことはできません。
```

下記の例ではin dicになっていますが､dicに.keysが補完されるようです｡

```vb
Dim b As Variant
For Each b In dic
  Debug.Print b, dic(b)
Next b
```

## 注意点
格納されていないkeyを参照すると、
dic(key) = EMPTY
が生成されます。
dic.countで要素数が返ってきますが、上記のEMPTYが入っているものも要素数の中にカウントされることになります。
forで回したりする場合はご注意ください。
For i = 1 To dic.Count
  If dic(i) <> 0 Then
    '......
  End If
Next i
などとすると予期せぬ要素が追加される可能性があります。
Dictionaryにはインデックスという概念はないようです。
keyに連番を振って、それをforで回すという方法ではなく、
for eachでkeyを取得し、keyを使う方法をオススメします。

## サンプル(pivot)
サンプルとして簡単なpivotを作ってみます。データの個数を数えるだけのものです。
activesheetのA列にあるデータがそれぞれいくつあるかを数えます。
Dictionaryの使い方は、keyを個々のデータにします。valueはインクリメントしていき、個数を表すようにします。

```vb:sample_pivot.bas
Public Sub sample_pivot()
  Dim ws As Worksheet
  Dim y  As Long
  Dim dic, a, k
  Set dic = CreateObject("scripting.dictionary")
  a = ActiveSheet.UsedRange.Value
  For y = LBound(a) To UBound(a)
    k = a(y, 1)
    dic(k) = dic(k) + 1
  Next y
  y = 0
  Set ws = ActiveWorkbook.Worksheets.Add
  For Each k In dic
    y = y + 1
    ws.Cells(y, 1).Value = k
    ws.Cells(y, 2).Value = dic(k)
  Next k
End Sub
```

出力の前にkeysを取得し、そのkeyを並び替えてから呼び出せばエクセルのpivotのようになります。

## おわりに
説明が下手で理解しづらいかもしれませんが、ぜひDictionaryを活用して、ご自身のマクロの中にpivotを組込んでみたりしてください。
間違っているとこや、誤字がありましたらご指摘ください。
Dictionaryのうまい活用方法とかもぜひ教えてください。