🌀

再帰呼び出しでネストした辞書(dict)を生成する

2022/08/12に公開

やりたいこと

  • あいうえおA という文字列から、 {'あ': {'い': {'う': {'え': {'お': 'A'}}}}} という辞書を作る
    • 整形するとこんな感じ:
      {
          'あ': {
              'い': {
                  'う': {
                      'え': {
                          'お': 'A'
                      }
                  }
              }
          }
      }
      
  • 生成した辞書に対して、同様に あいういあB という文字列を渡すと、共通する部分を整理して {'あ': {'い': {'う': {'え': {'お': 'A'}, 'い': {'あ': 'B'}}}}} のようにする
    • 整形するとこんな感じ:
      {
          'あ': {
              'い': {
                  'う': {
                      'え': {
                          'お': 'A'
                      },
                      'い': {
                          'あ': 'B'
                      }
                  }
              }
          }
      }
      

愚直にやるならこんな感じですかね。ちょっと気が遠くなりそうです。

d = {}
d["あ"] = {}
d["あ"]["い"] = {}
d["あ"]["い"]["う"] = {}
d["あ"]["い"]["う"]["え"] = {}
d["あ"]["い"]["う"]["え"]["お"] = "A"
d["あ"]["い"]["う"]["い"] = {}
d["あ"]["い"]["う"]["い"]["あ"] = "B"

print(d)

関数の再帰呼び出しを使ってみる

def dict_mapper1(base_dict, s, mapped):
    if type(base_dict) is not dict:
        return base_dict
    if len(s) == 1:
        base_dict[s] = mapped
        return base_dict
    if len(s) < 1:
        return
    head = s[0]
    rest = s[1:]
    try:
        base_dict[head] = dict_mapper1(base_dict[head], rest, mapped)
    except:
        base_dict[head] = dict_mapper1({}, rest, mapped)
    return base_dict

if __name__ == '__main__':

    d = {}
    d = dict_mapper1(d, "あいうえお", "A")
    d = dict_mapper1(d, "あいういあ", "B")
    print(d) # => {'あ': {'い': {'う': {'え': {'お': 'A'}, 'い': {'あ': 'B'}}}}}

関数の冒頭で下記のようにしているのは、引数を最短マッチさせるためです。

if type(base_dict) is not dict:
    return base_dict

つまり、d = dict_mapper1(d, "あいういあ", "B")d = dict_mapper1(d, "あい", "C") との両方があったときに、割り当ての順番に関係なく短い方を優先して下記のように出力されます。

{'あ': {'い': 'C'}}

Python ならでは(?)の方法

Python の引数にミュータブル(変更可能)なオブジェクトを渡すと、関数内から呼び出し元の変数を更新できます。これを利用すると return なしで書くこともできます。

def dict_mapper2(base_dict, s, mapped):
    if type(base_dict) is not dict:
        return
    if len(s) == 1:
        base_dict[s] = mapped
        return
    if len(s) < 1:
        return
    head = s[0]
    rest = s[1:]
    try:
        base_dict[head]
    except:
        base_dict[head] = {}
    dict_mapper2(base_dict[head], rest, mapped)


if __name__ == '__main__':

    d = {}
    dict_mapper2(d, "あいうえお", "A")
    dict_mapper2(d, "あいういあ", "B")
    print(d) # => {'あ': {'い': {'う': {'え': {'お': 'A'}, 'い': {'あ': 'B'}}}}}

感想:エッシャーの『描く手』 を思い出しました。

Discussion