【Python】入れ子dictを一段のdictにする関数でfilterとラムダ式と再帰関数を一気に学ぶ

プログラミング初心者の勉強ブログ #95

Pythonオンライン学習教材として評判のPyQ(https://pyq.jp/)によるPython学習記録です。Rubyを多少かじり、第二外国語としてPythonを少しづつ学んでいきます。(内容はRubyとの比較が多くなるかもしれません。)

今回はPyQの「Pythonチャレンジ」で出てきた「入れ子dictを一段のdictにする関数」から、filter()とlambdaと再帰関数を学んでいきます。初心者である自分がPython知るのにいい感じの要素が複数あったのでピックアップします。

 

目次

[toc]

 

filter関数とラムダ式を取り入れた再帰関数

def dict_flatten(target, separator):
    if not isinstance(target, dict):
        raise ValueError
    
    if not any(filter(lambda x: isinstance(x, dict), target.values())):
        return target
    
    dct = {}
    for key, value in target.items():
        if isinstance(value, dict):
            for k, v in dict_flatten(value, separator).items():
                dct[key + separator + k] = v
        else:
            dct[key] = value
            
    return dct


if __name__ == '__main__':
    print(dict_flatten({"foo": {"bar": "baz"}, "hoge": "fuga"}, "_"))


# --> {"foo_bar": "baz", "hoge": "fuga"}

 

この関数は引数に「dict」と「セパレーター」を取ることで、

dict_flatten({"foo": {"bar": "baz"}, "hoge": "fuga"}, "_")

# --> {"foo_bar": "baz", "hoge": "fuga"}

入れ子になったdictをこのような形で一段に変えることができます。

 

この中でPythonの組み込み関数は複数使われており、

  • isinstance( )
  • lambda
  • filter( )
  • any( )

これらを用いてこのdict_flatten( )関数は作られてます。一つずつみていきます。

 

isinstance(arg1, arg2)

isinstance関数は型判別を行う関数です。arg1の値がarg2の型であるかどうかを判定し、TrueかFalseを返します。

target = {"foo": {"bar": "baz"}, "hoge": "fuga"}
if not isinstance(target, dict):
    raise ValueError

上の場合Trueが返るのでValueErrorはraiseされません。

ちなみに

target = {"foo": {"bar": "baz"}, "hoge": "fuga"}
if not type(target) is dict:
    raise ValueError

のようにtype関数を使っても表現できます。

 

lanbda

lambdaを使うと、Pythonで無名関数をつくれます。関数は基本「def hoge(arg):」みたいな形で命名し、下に処理を続けますが、ラムダは名前をつけず、1行で簡易的な関数を作成してくれます。

lambda x: isinstance(x, dict)

これは、

def function(x):
    isinstance(x, dict)

この関数と同じことをします。

ラムダは「lambda 引数: 返値」の形で定義し、

x = {"bar": "baz"}
lambda x: isinstance(x, dict)

上の場合 {“bar”: “baz”}はdictなのでTrueを返します。

 

filter(function, iterable)

filterは、第二引数に指定したlistやdictなどのイテラブルに対し、第一引数に指定した関数を当ててTrueの要素のみを抽出することができる関数です。function(第一引数)にはlambdaを利用できます。

target = {"foo": {"bar": "baz"}, "hoge": "fuga"}
filter(lambda x: isinstance(x, dict), target.values())

上の場合、target.values()でtargetのvalueを取得し、ラムダ式のisinstance関数に引数として渡します。

target = {"foo": {"bar": "baz"}, "hoge": "fuga"}
filter(lambda x: isinstance(x, dict), target.values())

# ①target.values()
# --> {"bar": "baz"}、"fuga"

# ②lambda x: isinstance(x, dict)   ※xに①の {"bar": "baz"}、"fuga"が順に代入される
# --> def function(x):
#         return isinstance(x, dict)

最終的にtarget.values()からdictに該当する{“bar”: “baz”}がTrueになり、抽出されます。

 

any(iterable)

anyは、引数にとったイテラブルのいずれかにTrueがあればTrueを返す関数です。

target = {"foo": {"bar": "baz"}, "hoge": "fuga"}
if not any(filter(lambda x: isinstance(x, dict), target.values())):
    return target

この場合、先ほどのfilter関数ではTrueが出るのでany関数もTrueを返します。今回はif notなので、targetを返してdict_flatten( )関数をreturnで抜け出しません。

 

これらを確認した上でもう一度関数を見てみます。

 

再帰関数

def dict_flatten(target, separator):
    if not isinstance(target, dict):
        raise ValueError
    
    if not any(filter(lambda x: isinstance(x, dict), target.values())):
        return target
    
    dct = {}
    for key, value in target.items():
        if isinstance(value, dict):
            for k, v in dict_flatten(value, separator).items():
                dct[key + separator + k] = v
        else:
            dct[key] = value
            
    return dct


if __name__ == '__main__':
    print(dict_flatten({"foo": {"bar": "baz"}, "hoge": "fuga"}, "_"))


# --> {"foo_bar": "baz", "hoge": "fuga"}

再帰が行われているのは、

dct = {}
for key, value in target.items():
    if isinstance(value, dict):
        for k, v in dict_flatten(value, separator).items():
            dct[key + separator + k] = v
        else:
            dct[key] = value
    
    return dct

ここです。 「for k, v in dict_flatten(value, separator).items():」で、dict_flatten関数内部でもう一度dict_flatten関数を起動してます。この再帰があることで何重の入れ子dictであっても対応できます。繰り返しのループ処理と言ったらwhileがまず最初に頭に浮かぶので関数を再帰させる考え方も、これからは頭に入れておきたいです。

 

まとめ

アウトプット力の高い人が制すと言っても過言ではない(独断)このプログラミング学習界隈において、アウトプットを怠らない人は強いなと思います。努力できることは才能とよく聞くけど、ここ最近まじでそう思うようになりました。自分の努力の才能偏差値は自分が判断するものではなく自分以外の誰かが判断するので、自分で正確に知る術はありません。そもそも定量的な基準がない以上さじ加減でコロコロ変わります。しかし自分は自分以外の誰かの努力の才能偏差値を判断することができます。意識の高さとは自制心の強さであり(独断)、決してTwitterで量産型名言を連打し、いいね!という共感を多数獲得することではないのです。しかしフォロワー数が多い人を評価する自分は間違いなく存在します。これは明らかな矛盾であり、世の中の人間はみな他人を判断するための自分の物差しに自信がない証拠とも言えます。もし世界中の人間が「なにかを判断するための明確な基準をそれぞれが持ち、それに絶対的な自信がある」世の中だとしたら、全ての大衆心理は無くなり新世界が生まれるかもしれません。こういう漫画誰か書いてもいいんじゃないでしょうか。「僕は新世界の神になる」とか「偉大なる航路の後半」とか「暗黒大陸の新世界紀行」とか、新世界は数々のジャンプ漫画で題材にされる人気コンテンツですが、内容的にはヤングジャンプ寄りな感じになりそうですね。

まとめに全然関係のないことを書くスタイルいいかもしれない。東海オンエアの概要欄みたいな空気感で書こうと思う。

以上ありがとうございました。

 

返信を残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA