先日、どうしてプログラマに・・・プログラムが書けないのか?の記事を読んで、はじめてFizzBuzzに挑戦してみました。
# coding: utf-8 for i in range(1,101): if i % 15 == 0: print 'FizzBuzz' elif i % 3 == 0: print 'Fizz' elif i % 5 == 0: print 'Buzz' else: print i
書けました。 さすがPythonですね。キレイにまとまっていると思います。
次は、map
やスライスをつかったもっとPythonらしいコードを書きてみましょう。
# coding: utf-8 def fizzbuzz(i): a, b = i%3==1, i%5==1 print 'FizzBuzz'[0 if a else 4:8 if b else 4] if a or b else i map(fizzbuzz, range(1, 101))
関数fizzbuzzを定義して、renge関数からの返り値のリストをmapする方式ですね。
また、変数a,bは多値返却をつかって定義し、文字列FizzBuzzはスライスをつかって分解しました。スライスの値は三項演算子のPython版にあたる条件演算子をつかって計算しています。
なんだか面白くなってきました。
print'\n'.join(['FizzBuzz'[4 if i%3 else 0:4 if i%5 else 8] or str(i) for i in range(1,101)])
リスト内包表記をつかってワンライナーで書いてみました。
条件演算子内の演算は、integer型は0ではない数字がTrueとして扱われるので、先ほどのコードとは順番が逆になっています。
また、Pythonでは、空文字列をBool型に型変換するとFalseになり、orの左辺がFalseだと右辺が返るという性質があるので、この場合str(i)
が返ります。
ここでstrに型変換しているのは、join
関数はinteger型を混ぜるとエラーを吐くからです。
こうなってくると、さっきまでなかった\n
とかjoin
とかstr
とかが気になってしまいますね。
for i in range(1,101):print'FizzBuzz'[4if i%3 else 0:4if i%5 else 8]or i # 読みやすく for i in range(1,101): print 'FizzBuzz'[4 if i%3 else 0:4 if i%5 else 8 ] or i
リスト内包表記へのこだわりを捨てました。
キレイにロジックがだけのこったFizzBuzzなコードになったと思います。
ここで、怖いもの見たさに最短のFizzBuzzを検索してみました。
結果がこちらです。
# 56 bytes (世界トップ) for i in range(100):print i%3/2*'Fizz'+i%5/4*'Buzz'or-~i # 読みやすく for i in range(100): print i%3/2*'Fizz' + i%5/4*'Buzz' or -~i
…すごいですね。
range(100)
にすることにより、でi%3/2
が、整数値/整数値=整数値を返すというPythonの仕様により、3おきに1を返すようになっています。
for i in range(10): print i, i%3/2 0 0 1 0 2 1 3 0 4 0 5 1 6 0 7 0 8 1 9 0
また、~
はビット反転ですがPyhonの場合は、~i == -(i + 1)
というせいしつがあるので、-~i == -(-(i + 1)) == i + 1
となります。これで、単純な演算だけでFizzBuzzしています。
これを参考にして、bytes数の少ない新しいFizzBuzzを作ってみました。
できるだけ予約後を使わないで、数値演算だけで、ロジックを組めるようにしました。
# 66 bytes for i in range(100):print'FizzBuzz'[[4,0][i%3/2]:(i%5/4+1)*4]or-~i # 読みやすく for i in range(100): print 'FizzBuzz'[ [4,0][ i%3/2 ]:( i%5/4 +1)*4 ] or -~i
スライスの箱の前項はPythonの仕様上bit反転できないので、このような形になってしまいました。
後項は返り値がどちらも4の倍数なので、条件演算と同時にそこで生じる1を有効利用する形にしました。
おわりに
Pythonだとキレイに書くこともできるし、パズルっぽく書くこともできて面白いですね。
最初は純粋にFizzBuzzを解くつもりでしたが、途中で目的が二転三転して、最終的には「最短のFizzBuzzに近づける」になっていた気がします。
番外編
lamdaを使ってワンライナーで見やすくなるかどうかを検証したものです。
結果、普通に計算でなんとかする方がbyte数的にはいい結果になります。
Pythonは、グローバルスコープと関数スコープしかないので、lamdaには変数i
を渡さないでも勝手に計算してくれるので何やらむず痒いコードができます。
# 圧縮すると93 bytes for i in range(1,101): print (lambda g:g(3,'Fizz') + g(5,'Buzz') or i)(lambda j,s:''if i%j else s) # 圧縮すると101 bytes for i in range(1,101): print (lambda l:'FizzBuzz'[l(3,0,4):l(5,8,4)] or i)(lambda j,t,f:f if i%j else t))