Pythonの学習を始めたばかりです。ラムダ関数に出くわしました。問題の 1 つで、著者は階乗用のワンライナー ラムダ関数を書くように依頼しました。
これは与えられた解決策です:
num = 5
print (lambda b: (lambda a, b: a(a, b))(lambda a, b: b*a(a, b-1) if b > 0 else 1,b))(num)
奇妙な構文が理解できません。a(a,b) とはどういう意味ですか?
誰か説明できますか?
ありがとう
Pythonの学習を始めたばかりです。ラムダ関数に出くわしました。問題の 1 つで、著者は階乗用のワンライナー ラムダ関数を書くように依頼しました。
これは与えられた解決策です:
num = 5
print (lambda b: (lambda a, b: a(a, b))(lambda a, b: b*a(a, b-1) if b > 0 else 1,b))(num)
奇妙な構文が理解できません。a(a,b) とはどういう意味ですか?
誰か説明できますか?
ありがとう
階乗自体は、ほぼ期待どおりです。a
あなたはそれが... 階乗関数であると推測します。b
は実パラメータです。
<factorial> = lambda a, b: b*a(a, b-1) if b > 0 else 1
このビットは、階乗の適用です。
<factorial-application> = (lambda a, b: a(a, b))(<factorial>, b)
a
階乗関数そのものです。それ自体を最初の引数とし、評価点を 2 番目の引数とします。これは、気にしない限り次のように一般化できrecursive_lambda
ます。a(a, b - 1)
a(b - 1)
recursive_lambda = (lambda func: lambda *args: func(func, *args))
print(recursive_lambda(lambda self, x: x * self(self, x - 1) if x > 0 else 1)(6))
# Or, using the function verbatim:
print(recursive_lambda(lambda a, b: b*a(a, b-1) if b > 0 else 1)(6))
したがって、外側の部分があります。
(lambda b: <factorial-application>)(num)
ご覧のとおり、呼び出し元が渡さなければならないのは評価ポイントだけです。
実際に再帰ラムダが必要な場合は、ラムダに次のように名前を付けることができます。
fact = lambda x: 1 if x == 0 else x * fact(x-1)
そうでない場合は、単純なヘルパー関数を使用できます。ret
ラムダが自身を参照できなかった前のコードとは異なり、これは自身を参照できるラムダであることがわかります。
def recursive_lambda(func):
def ret(*args):
return func(ret, *args)
return ret
print(recursive_lambda(lambda factorial, x: x * factorial(x - 1) if x > 1 else 1)(6)) # 720
どちらの方法でも、ラムダをそれ自体に渡すばかげた手段に頼る必要はありません。
この 1 つのライナーをタマネギのように剥がしてみましょう。
print (lambda b: (Y))(num)
無名関数を作成し (キーワード lambda は、一連のパラメーター名、コロン、それらのパラメーターを使用する関数を入力しようとしていることを意味します)、その 1 つのパラメーターを満たすために num を渡します。
(lambda a, b: a(a, b))(X,b)
ラムダの内部で、別のラムダを定義します。このラムダを Y と呼びます。これは、a と b の 2 つのパラメーターを取ります。a は a と b で呼び出されるため、a はそれ自体ともう 1 つのパラメーターを取る呼び出し可能オブジェクトです。
(lambda a, b: b*a(a, b-1) if b > 0 else 1
,
b)
これらは Y へのパラメーターです。最初のものはラムダ関数で、X と呼びます。X が階乗関数であり、2 番目のパラメーターがその数値になることがわかります。
つまり、上に行って Y を見ると、次のように呼び出すことがわかります。
X(X, b)
どちらがしますか
b*X(X, b-1) if b > 0 else 1
そして自分自身を呼び出し、factorial の再帰部分を形成します。
外側をずっと見てみると、b は最も外側のラムダに渡した num であることがわかります。
num*X(X, b-1) if num > 0 else 1
これは紛らわしいワンライナーとして書かれたので、ちょっと紛らわしいです:)
この関数には 2 つの難しい部分があります。
1. lambda a, b: b*a(a, b-1) if b > 0 else 1
.
2. 1 の後に続く「b」。
1 の場合は、次のとおりです。
def f(a, b):
if b > 0:
b * a(a, b - 1)
else:
1
2 の場合、この b
(lambda b: (lambda a, b: a(a, b))(lambda a, b: b*a(a, b-1) if b > 0 else 1,b))(num)
(this one)
実際にこれはbです:
(lambda b: (lambda a, b: a(a, b))(lambda a, b: b*a(a, b-1) if b > 0 else 1,b))(num)
(this one)
その理由は、2 番目と 3 番目のラムダの定義内にないため、最初の b を参照するためです。
num を適用して外側の関数を取り除いた後:
(lambda a, b: a(a, b)) (lambda a, b: b*a(a, b-1) if b > 0 else 1, num)
タプルに関数を適用するだけです (lambda a, b: b*a(a, b-1) if b > 0 else 1, num)
このタプルを (f, num) と呼びましょう (f の定義は上にあります)それに適用lambda a, b: a(a, b)
すると、取得します
f(f, 数値)。
num が 5 であると仮定します
。f の定義により、最初に評価されます。
5 * f(f, 4)
次に:
5 * (4 * f(f, 3))
までずっと
5 * (4 * (3 * (2 * (1 * f(f, 0)))))
f(f, 0) は 1 になります。
5 * (4 * (3 * (2 * (1 * 1))))
では、5 の階乗です。
再帰ラムダの一般化された定義は次のとおりです
recursive_lambda = (lambda func: lambda *args: func(func, *args))