Arantium Maestum

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

PythonでリテラルなしIfなしFizzBuzzワンライナー

前回の記事FizzBuzzコードをちょこっといじってこんな感じで満足していた:

そうしたらこんなツイートが:

え、IfもなしでFizzBuzzを・・・?

出来らあ!

と啖呵を切って考えてみる。まあ前回のコードでも利用していたand/orの特性を使えばifは当然省けるはず。

Pythonだと真偽値以外もTruthinessを持ち、特に空文字列は偽なので("Fizz" if x%3==0 else "")+("Buzz" if x%5==0 else "") or xのようなロジックを使うのが一番スマート。

もう一つハック的なポイントとして、PythonTrueFalseはほぼ10と同等なので("Fizz" * (x%3==0))+("Buzz" * (x%5==0)) or xと書ける。

これでコア部分のロジックはifなしでいけそう。

前回の記事でも紹介した通り、"Fizz", "Buzz", 3, 5はリテラルなしで簡単に作れる。しかし0は(無名関数の定義の仕方のせいで)作れなくなっている。f(f)-f(f)のようにして作ってもいいのだが、長くなるのでイマイチ。

まず考えたのは関数内の式の順番を入れ替えるやり方:

FizzBuzzのプリント・ループ処理に入る条件をA!=idにして、len(a)は関数の最後に持ってきた。これでf()が0になる。

しかし、よく考えるとf()で0を表現するまでもなく、プリント・ループ処理に入った時点でlen(a)という0の値をすでに評価していることに気づいた。この値を変数束縛して使ってしまえばいい。

さらに二つ修正点がある:

  • X if Y else Zという構文を使っていた時はXZの部分は評価されないケースがあった。それに対してX + Y or Zの場合、Xの部分は必ず評価される。なのでX(k:=sorted(f.__kwdefaults__))という変数束縛を持ってこれる
  • 0が表現できるので仮引数をソートして"Buzz"が先頭に来ても問題ない。ので仮引数にAを使う必要がなくなり、"Fizz"と"Buzz"をアクセスするインデックスに1と0を使える(文字数を減らせる)。

これらを踏まえて、とりあえず現在の最終形:

ifなしという制約がつく前に比べて文字数が減ってる・・・ (この記事の冒頭のコードが211文字、最終形のコードが162文字)

制約がついて工夫しないといけなくなるといいアイデアが出る、というケース。

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