【Python】ディクショナリ（辞書）の基本と使いどころと具体例

「Pythonを勉強していたらディクショナリが出てきて良く分からない！」
「データ構造ってなに？ディクショナリってどの場面でどう使ったらいいの？」
という人向けの記事です。

使い方を具体例で示していますので参考になれば嬉しいです。

## ディクショナリとは

ディクショナリ（辞書）はデータ構造の一つです。

`データ構造とは、プログラミングにおいてデータを効率的に格納する形式や方法のこと`

簡単ですね！

プログラミングの”基本的”なデータ構造は次のたった2つだけです。

- リスト（配列）：複数の要素を順序付けて格納することができるデータ構造
- ディクショナリ（辞書）：キーとバリュー（値）のペアを格納するデータ構造

この2つが上手く使えれば、だいたいの業務プログラムは上手く書けるようになるため、この機会にディクショナリの使い方を押さえておきましょう！

## ディクショナリを使用するメリット

- 効率的にデータを格納し、取り出すことができる
- データの集計やグルーピングができる
- 大容量のデータであっても高速にアクセスできる

ディクショナリはシンプルなデータ構造ですが、上記のメリットから分かるようにそのポテンシャルは絶大であり、プログラミングにおいては欠かせない存在となっています。

## ディクショナリの基本的な使い方

### ディクショナリの作成

```python
# 空のディクショナリの作成する2つの方法
my_dict = dict()
my_dict = {}        # より簡潔なこちらを良く使う

# 要素を入れた状態で初期化
my_dict = {
    "key1": "value1",
    "key2": "value2",
    "key3": "value3",
}
```


- "key1", "key2", "key3" をディクショナリの **キー** といいます
- "value1", "value2", "value3" をディクショナリの **バリュー(値)** といいます
- "key1"と"value1"などキーとバリューのペアのことを **アイテム（要素）** といいます

以降は次の初期化されたディクショナリを前提として説明します。

```python
my_dict = {
    "key1": "value1",
    "key2": "value2",
    "key3": "value3",
}
```

### バリュー（値）の取得

キーを指定することでバリューが取り出せます。

```python
# 2つの取得方法
print(my_dict["key1"])      # "value1" を出力
print(my_dict.get("key1"))  # "value1" を出力
```

特に制約がなければ単に `my_dict["key1"]` とすることが多い

この2つの方法では、指定したキーがディクショナリに存在しない場合に違いがあります。

```python
# my_dictに存在しない"key10"をキーに指定する
print(my_dict["key10"])        # 例外（エラー）が発生しプログラムが終了する
print(my_dict.get("key10"))    # None を出力
```

### アイテムの追加

```python
my_dict["new_key"] = "new_value"
```

### アイテムの削除

```python
del my_dict["new_key"]
```

### キーの存在チェック

```python
if "key3" in my_dict:
    print("key3は存在します。")
```

「キー in ディクショナリ」とすることで、キーが存在する場合は **True**、存在しない場合は **False** のbool値が得られます。

### すべてのキーの取得

```python
# for文で1つずつ取り出す
for key in my_dict:
    print(key)

# keys()メソッドでキーを取り出すことを明示的に書く
for key in my_dict.keys():
    print(key)

# キーをリストとして取り出す
print(list(my_dict.keys()))
```

### すべてのバリューの取得

```python
# for文で1つずつ取り出す
for key in my_dict.values():
    print(key)

# リストとして取り出す
print(list(my_dict.values()))
```

### すべてのアイテム（キーとバリューのペア）の取得

```python
# for文で1アイテムずつ取り出す
for key, value in my_dict.items():
    print(key, value)

# リストとしてアイテム（キー、バリューのペア）を取り出す
items = list(my_dict.items())
print(items)    # [('key1', 'value1'), ('key2', 'value2'), ('key3', 'value3')]
```

### ディクショナリのデータをリストにソートする

キーのソート

```python
print(sorted(my_dict.keys()))
```

バリューのソート

```python
print(sorted(my_dict.values()))
```

アイテムのソート

```python
# キーを基準にソートする場合
print(sorted(my_dict.items()))

# バリューを基準にソートする場合
print(sorted(my_dict.items(), key=lambda x: x[1]))
```

## ディクショナリの使いどころと具体例

以下のような場面で良く使用されます。

- データのマッピング
- データの集計
- データのグルーピング
- データのマッピング（階層あり）

それでは具体例を見ていきましょう。

### データのマッピング

**社員IDをキーとして名前を取り出す例**

以下のようなデータが与えられ、社員IDをキーとしてデータを処理したい場面が良くあります。

| 社員ID | 名前      |
| -------- | --------- |
| 22001    | 保栄茂 真 |
| 22032    | 理原 東聖 |
| 23044    | 真汐 犬助 |
| 24085    | 高着 力   |
| 24093    | 西明 昭男 |

```python
# 社員IDと名前のペアのデータがリストで与えられた場合。（実際にはファイルなどから読み込む場合が多い）
data = [
    ["22001", "保栄茂 真"],
    ["22032", "理原 東聖"],
    ["23044", "真汐 犬助"],
    ["24085", "高着 力"],
    ["24093", "西明 昭男"],
]

# 社員IDをキー、名前をバリューとしたディクショナリを作成
d = {}
for shain_id, name in data:
    d[shain_id] = name

# 作成したディクショナリを出力
print(d)
"""出力結果
{
    "22001": "保栄茂 真",
    "22032": "理原 東聖",
    "23044": "真汐 犬助",
    "24085": "高着 力",
    "24093": "西明 昭男",
}
"""

# 社員IDを指定して名前を取得する
print(d["24085"])  # "高着 力"
```

社員IDをキー、名前をバリューとしてディクショナリを作成することで、社員IDを指定すれば検索することなく名前が取り出せます。
名前をリストとして管理した場合にはできない操作ですね。

### データの集計

**血液型ごとに人数を集計する例**

| 名前      | 血液型 |
| --------- | ------ |
| 保栄茂 真 | A      |
| 理原 東聖 | B      |
| 真汐 犬助 | B      |
| 高着 力   | O      |
| 西明 昭男 | A      |

A型2人、B型2人、O型1人の結果が得られるように集計してみましょう！

```python
# データがリストで与えられた場合
data = [
    ["保栄茂 真", "A"],
    ["理原 東聖", "B"],
    ["真汐 犬助", "B"],
    ["高着 力", "O"],
    ["西明 昭男", "A"],
]

# 血液型をキー、血液型の人数をバリューとして集計
d = {}
for name, btype in data:
    if btype not in d:
        d[btype] = 0
    d[btype] += 1
    
# 集計後のディクショナリを出力
print(d)
"""出力結果
{
    "A": 2,
    "B": 2,
    "O": 1,
}
"""
```

血液型をキーとして人数をカウントすることで集計が行えました

血液型がディクショナリのキーに存在しない場合を `if btype not in name_to_btype` でチェックして人数を0で初期化する部分がポイントですね

### データのグルーピング

**血液型ごとに名前リストを作成する例**

| 名前      | 血液型 |
| --------- | ------ |
| 保栄茂 真 | A      |
| 理原 東聖 | B      |
| 真汐 犬助 | B      |
| 高着 力   | O      |
| 西明 昭男 | A      |

A型は["保栄茂 真", "西明 昭男"]、B型は["理原 東聖", "真汐 犬助"]、O型は["高着 力"] となるようにグルーピングしてみましょう！

```python
# データがリストで与えられた場合
data = [
    ["保栄茂 真", "A"],
    ["理原 東聖", "B"],
    ["真汐 犬助", "B"],
    ["高着 力", "O"],
    ["西明 昭男", "A"],
]

# 血液型をキー、血液型の名前リストをバリューとしてディクショナリを作成
d = {}
for name, btype in data:
    if btype not in d:
        d[btype] = []
    d[btype].append(name)
    
# 作成したディクショナリを出力
print(d)
"""出力結果
{
    "A": ["保栄茂 真", "西明 昭男"],
    "B": ["理原 東聖", "真汐 犬助"],
    "O": ["高着 力"],
}
"""

# B型の名前リストを取得
print(d["B"])  # ["理原 東聖", "真汐 犬助"]
```

キーに対応するデータをまとめられるのがディクショナリの特徴です。

バリューとして保持できるのはのは数字や文字列だけでなく、リストなどのデータ構造も可能なので応用は無限ですね。

### ディクショナリの階層でのデータ管理

**社員IDをキーに名前と血液型をそれぞれ取り出す例**

| 社員ID     | 名前      | 血液型 |
| ---------| --------- | ------ |
| 22001    | 保栄茂 真 | A      |
| 22032    | 理原 東聖 | B      |
| 23044    | 真汐 犬助 | B      |
| 24085    | 高着 力   | O      |
| 24093    | 西明 昭男 | A      |

これまではキーに対応するバリューを取り出す、という1対1の対応でした。
今回はキーである社員IDに対して、名前と血液型の２つのバリューがある場合です。
このような場合は、<INS>ディクショナリに階層を持たせる</INS>ことで上手くデータを管理できるようになります。

```python

# データがリストで与えられた場合
data = [
    ["22001", "保栄茂 真", "A"],
    ["22032", "理原 東聖", "B"],
    ["23044", "真汐 犬助", "B"],
    ["24085", "高着 力", "O"],
    ["24093", "西明 昭男", "A"],
]

# 社員番号をキーとしてディクショナリを作成
d = {}
for shain_id, name, dtype in data:
    # dのバリューに"name"と"dtype"をキーとしたディクショナリを追加
    d[shain_id] = {
        "name": name,
        "dtype": dtype,
    }
    
# 作成したディクショナリを出力
print(d)
"""出力結果
{
    "22001": {
        "name": "保栄茂 真",
        "dtype": "A",
    },
    "22032": {
        "name": "理原 東聖",
        "dtype": "B",
    },
    "23044": {
        "name": "真汐 犬助",
        "dtype": "B",
    },
    "24085": {
        "name": "高着 力",
        "dtype": "O",
    },
    "24093": {
        "name": "西明 昭男",
        "dtype": "A",
    },
}
"""

# 社員番号24085の名前と血液型をそれぞれ取得
print(d["24085"]["name"])   # "高着 力"
print(d["24085"]["dtype"])  # "O"
```

ディクショナリのバリューにディクショナリを設定することでデータを階層構造で保存できました。

## ディクショナリ問題集

理論的に理解しているかよりも使いこなせるかの方がはるかに重要なので、たくさん問題を解いて使い方を身に着けていきましょう。

ディクショナリを使用する問題をまとめているサイト
[競プロ連想配列練習問題](https://shiki-sci.jp/kyopro/map/)

### おすすめの問題

[https://atcoder.jp/contests/abc349/tasks/abc349_b](https://atcoder.jp/contests/abc349/tasks/abc349_b)

ディクショナリの特性を活かした、基本的だけどひと工夫いる問題なのでぜひ挑戦してみてください。

<details><summary>ヒント１</summary>
それぞれの文字が何回出現するかディクショナリでカウントしてみましょう！
</details>

<details><summary>ヒント2</summary>
ヒント1の結果を利用して、出現する回数ごとに文字の種類をカウントしてみましょう

```python
# 最終的にこのようなディクショナリを作成する
d = {
    "1回出現する文字の種類": 2,
    "2回出現する文字の種類": 2,
    "3回出現する文字の種類": 1,
}
```
</details>


<details><summary>解答</summary>

```python
# 判定する文字列を受け取る
s = input()

# それぞれの文字が何回出現するかディクショナリでカウント
d = {}
for char in s:
    if char not in d:
        d[char] = 0
    d[char] += 1

# 出現する回数ごとに文字の種類をカウント
dd = {}
for char_cnt in d.values():
    if char_cnt not in dd:
        dd[char_cnt] = 0
    dd[char_cnt] += 1

# 良い文字列か判定
for type_cnt in dd.values():
    if type_cnt != 0 and type_cnt != 2:
        print("No")
        exit()
print("Yes")
```

ディクショナリでカウントする操作を2回行うと解ける問題でしたね。

</details>