PythonでFizzBuzzしてみた

先日、どうしてプログラマに・・・プログラムが書けないのか?の記事を読んで、はじめて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))