Arantium Maestum

プログラミング、囲碁、読書の話題

PythonでリテラルなしIfなし変数束縛なしFizzBuzzワンライナー〜ラムダの力を讃えよ〜

前回こう書いた通り:

追記:前回の記事に書き忘れたが、walrus operatorことAssignment Expressionsを多用しているのでPython3.8以降が必要。

これまで見てきたワンライナーはPython3.8から導入された変数束縛式:=を使っていて、そのせいで最近のPythonバージョンに依存している。

このコードだと:

(f:=lambda ...)(z:=len(a))そして(k:=sorted(f.__kwdefaults__))の三ヶ所で利用している。

そのうち後者二つは文字数を減らすために使っているようなもので、冗長でいいなら省略できる:

(f:=lambda *a,x=id,Fizz=id,Buzz=id:
  len(a) or 
  print(
    sorted(f.__kwdefaults__)[f(f)]*(x%f(f,f,f)==len(a))+
    sorted(f.__kwdefaults__)[len(a)]*(x%f(f,f,f,f,f)==len(a))
    or x)
  or f(x=x+f(f)))(x=f(f))

(読みやすいように改行付き)

しかしfという関数名をつけるのは再帰的な定義の要。やっぱり変数束縛は必要だろう。

そのように安直に考えていると

ラムダを崇めよ、ラムダの力を讃えよ

どこからともなく迷える子羊を導く声が聞こえた。多分内なるアロンソ・チャーチかハスケル・カリーが語りかけてきているのだろう。あるいはラムダマンことフィル・ワドラー("Phil Wadler Lambda Man"で検索してください)かもしれない。

そう、ラムダに魂を惹かれた者なら誰でも知っている。再帰がしたいならFixed Pointを使えばいいじゃない。

つまり別の無名関数を使って、再帰させたい無名関数を自己適用することで、関数内でその関数自身が引数として束縛されている状況を作り出せる。

無名関数の中で使われるfを、外部環境を参照するのではなく、この無名関数の仮引数として追加してやる:

lambda *a,f=id,x=id,Fizz=id,Buzz=id:
  len(a) or
  print(
    sorted(f.__kwdefaults__)[f(f)]*(x%f(f,f,f)==len(a))+
    sorted(f.__kwdefaults__)[len(a)]*(x%f(f,f,f,f,f)==len(a)) or
    x) or
  f(f=f,x=x+f(f))

(最後の再帰的呼び出しの時にもちゃんとf=fと渡してやるのを忘れずに)

そしてこの無名関数を引数に受け取り、必要な引数を与えて呼び出すための別の無名関数を使えばいい:

(lambda f:f(f=f,x=f(f)))(
  lambda *a,f=id,x=id,Fizz=id,Buzz=id:
    len(a) or 
    print(
      sorted(f.__kwdefaults__)[f(f)]*(x%f(f,f,f)==len(a))+
      sorted(f.__kwdefaults__)[len(a)]*(x%f(f,f,f,f,f)==len(a)) or
      x) or
    f(f=f,x=x+f(f)))

ちょっと文字数を減らしたいならついでにkも仮引数に追加できる:

(lambda f:f(f=f,k=sorted(f.__kwdefaults__),x=f(f)))(
  lambda *a,f=id,k=id,x=id,Fizz=id,Buzz=id:
    len(a) or 
    print(
      k[f(f)]*(x%f(f,f,f)==len(a))+
      k[len(a)]*(x%f(f,f,f,f,f)==len(a)) or
      x) or
    f(f=f,k=k,x=x+f(f)))

というわけでWalrus Operator使わないで202文字にまとまる:

リテラルも変数束縛もいらない。ラムダさえあればいい・・・