【Python実践自作問題集】No.8 ネストされた辞書

# 1. はじめに

この問題集は、Pythonの基礎を習得した後、次の段階へ進みたい人のサポートをすることが目的です。
また、競技プログラミングとは異なり、複雑なアルゴリズムの問題ではなく「**可読性の最大化**」に焦点を当てた問題が中心となります。

[目次](https://qiita.com/__Lily__/items/6f70cd6ae0e80593f09d)
前の問題：[不正な値を許すな](https://qiita.com/__Lily__/items/ab9dfb8bd0cb0383b641)
次の問題：


# 2. 問題
```python
"""
No.8 ネストされた辞書

次のように、keyが'.'区切りの文字列である辞書dictionaryが与えられます。
keyを'.'で分割し、ネストされた辞書を作成してください。
"""

from typing import Any

# 与えられる辞書の例（あくまで一例です）
dictionary: dict[str, Any] = {
    'food.fruit.apple': {'price': 150},
    'food.fruit': {'place': '1F'},
}

# 期待される結果
expected = {
    'food': {
        'fruit': {
            'apple': {'price': 150},
            'place': '1F',
        },
    },
}
```


# 3. 解答例

```python
from dictknife import deepmerge
from nesteddict import NestedDict

nested = {}
for key, value in dictionary.items():
    nested = deepmerge(nested, NestedDict({key: value}))
```

```python:1行で書く場合
from dictknife import deepmerge
from nesteddict import NestedDict

nested = deepmerge(*[NestedDict({key: value}) for key, value in dictionary.items()])
```


# 4. 採点基準

* 「怠惰」になれましたか？


# 5. 解説

まさか、**自力実装してしまいましたか？**
という冗談は置いといて、今回は知っていると便利なライブラリ2選の紹介です。

初めに`nesteddict`ライブラリですが、これはkeyが "." 区切りの辞書をネストされた辞書に変換してくれます。

```python
from nesteddict import NestedDict


dictionary = {
    'food.fruit.apple': {'price': 150},
}
nested = NestedDict(dictionary)
print(nested)
```

```text:実行結果
{'food': {'fruit': {'apple': {'price': 150}}}}
```

これだけで良いのでは？と思うかもしれませんが、実は落とし穴があります。
実際、例に挙げた辞書を変換すると期待通りになりません。

```python
from nesteddict import NestedDict


dictionary = {
    'food.fruit.apple': {'price': 150},
    'food.fruit': {'place': '1F'},
}
nested = NestedDict(dictionary)
print(nested)
```

```text:実行結果
{'food': {'fruit': {'place': '1F'}}}
```

`'apple'`以下のデータが消えてしまっていますね。
何が起きているのかというと、次の2つの辞書がマージされるときに2階層目以降のデータが上書きされてしまうのです。

```python
key_1 = 'food.fruit.apple'
dictionary_1 = NestedDict({key_1: dictionary[key_1]})
print(f'{dictionary_1=}')

key_2 = 'food.fruit'
dictionary_2 = NestedDict({key_2: dictionary[key_2]})
print(f'{dictionary_2=}')

merged = dictionary_1 | dictionary_2
print(f'{merged=}')
```

```text:実行結果
dictionary_1={'food': {'fruit': {'apple': {'price': 150}}}}
dictionary_2={'food': {'fruit': {'place': '1F'}}}
merged={'food': {'fruit': {'place': '1F'}}}
```

これを適切にマージするために、`dictknife`ライブラリの`deepmerge`関数を使用します。

[Pythonでネストしたdictをマージしたい - Qiita](https://qiita.com/rana_kualu/items/7c957851a058c1ae7808)

[dictknife,jsonknifeの機能を整理 - hatenablog.com](https://pod.hatenablog.com/entry/2017/05/27/234641)

```python
deep_merged = deepmerge(dictionary_1, dictionary_2)
print(f'{deep_merged=}')
```

```text:実行結果
deep_merged={'food': {'fruit': {'apple': {'price': 150}, 'place': '1F'}}}
```

期待通りにマージされていますね。
これを繰り返すことで、目的のネストされた辞書が得られます。

どちらも有名なライブラリではないですが、知っていると時折活躍してくれます。
例えばiniファイルを読み込むとき、セクション名が "." 区切りだとこの問題と同じ状況になります。

[Pythonでiniファイルを読み込むライブラリ「configparser」 - Qiita](https://qiita.com/T_Wakasugi/items/8e21ef6093df90a5e8d3)


# 6. テストコード

```python
from dictknife import deepmerge
from nesteddict import NestedDict


nested = {}
for key, value in dictionary.items():
    nested = deepmerge(nested, NestedDict({key: value}))

print(nested)
assert nested == expected
```

```text:実行結果
{'food': {'fruit': {'apple': {'price': 150}, 'place': '1F'}}}
```