FizzBuzzをPostScriptで ― コードゴルフに挑戦 Tweet
なんか、すげー久々のエントリのようですが、気のせいでしょう......多分。
Twitterで、何人かが「FizzBuzz」という言葉を書いているのを見掛けて気になったのですが、やはりTwitter経由で竹迫さんのエントリ、
FizzBuzz - Golf Challenge [TAKESAKO @ Yet another Cybozu Labs]
を見て、PostScriptで書いてみることにしました。課題としては、
1から100までの数をプリントするプログラムを書け。ただし3の倍数のときは数の代わりに「Fizz」と、5の倍数のときは「Buzz」とプリントし、3と5両方の倍数の場合には「FizzBuzz」とプリントすること。
というものです。とはいえ、PostScriptなんて、大学時代と前職の出版社で最初の数年間Hackしていただけで、いざ書こうとすると結構忘れていて、久々に「赤本」こと、PostScriptリファレンス・マニュアルを開きながらのコーディングとなりました。
ひとまず書いたのは、
1 1 100 { dup 15 mod 0 eq {(FizzBuzz)} {dup 3 mod 0 eq {(Fizz)} {dup 5 mod 0 eq {(Buzz)} if} ifelse } ifelse = clear } for
というものです。
PostScriptというのはプリンタ用の記述言語なんですが、このプログラムはあくまで標準出力に結果を表示します。実際にはプリンタとインタラクティブに通信してという環境は限られますので、PostScript互換のGhostscript上で実行しました。Adobe Acrobat Standardとかがあれば、Acrobat Distiller上でも実行できます(結果はDisiller上のログ画面かログファイルい出力)。ちなみに、それぞれでの実行結果はこんな感じです。
また、PostScriptは逆ポーランド記法のプログラム言語で、一般的なプログラム言語なら、
print 1+2;
とするところを、PostScriptの場合、
1 2 add =
などとします。
最初に書いたコードにもある、「=」というオペレータは、スタック上のオブジェクトを必要なら文字列に変換して出力するもので、文字列だろうと数値だろうと出力できますが、かならず改行されてしまいます。一方「print」というオペレータは出力後改行はしないものの、文字列以外は扱えません。
また、PostScriptでは文字列を連結する手段はあるのですが、他の最近の言語のように短いコードでは書けませんので、他の言語のようなアプローチはとれません。そこで、それらを踏まえて書いたのがこれです。
1 1 100{dup dup 3 mod 0 eq{(Fizz)print()}if exch 5 mod 0 eq{/Buzz}if = clear}for
これを、anarchy golf - FizzBuzzに登録してみたものの81バイトで、トップの50バイトには遠くかないませんが、悲しいかな、これ以上短いロジックは思い付きません。
anarchy golf では、サーバ側にそれぞれの言語の処理系があり、確実に動作して正しい結果を出したことは確実です。どんなコードなのか見ることはできませんが、Statisticsを見るとどんな種類の文字が使われているのかがわかり、上位のプレーヤーのコードにはバイナリが含まれていることがわかります。
PostScriptはコードをLZWなどで圧縮/エンコードすることもできるので、試しにやってみたりもしましたが、コードが短すぎるので、冗長性が少なく、圧縮の効果はまるでありません。昔PostScriptをHackしてたときは主にPostScript Level Iだったこともあり使ったことはなかったのですが、Level II以降でサポートされたバイナリエンコードというものがあったのを思い出して、リファレンスマニュアルを見て「バイナリトークンエンコード」を施してみました。一部の数値や文字列は敢えてそのままにすることで、短さを追求してみました。さらに、anarchy goldのPostScriptでは、実行後にスタックにゴミを残してもいいことがわかったので、スタックの内容をクリアしていた「clear」も不要ですので、
1 1 100{dup dup 3 mod 0 eq{(Fizz)print()}if exch 5 mod 0 eq{/Buzz}if =}for
をバイナリトークンエンコードしました。
00000000 88 01 31 88 64 7b 92 38 92 38 33 92 6a 30 92 3d |..1.d{.8.83.j0.=|
00000010 7b 28 46 69 7a 7a 29 92 76 28 29 7d 92 54 92 3e |{(Fizz).v()}.T.>|
00000020 35 92 6a 30 92 3d 7b 2f 42 75 7a 7a 7d 92 54 3d |5.j0.={/Buzz}.T=|
00000030 7d 92 48 |}.H|
これで、51バイトです。くやしいもののギブアップです。と、ふと、トップのySas氏の名前とFizzBuzzでググったところありました。なるほど、やられました。で、リファレンスマニュアルの第3版引っ張り出してきて読み返してみたら、
トークンの/は、その後に文字を記述しなくてもそれだけで有効なリテラル名である
とあるではありませんか。うーむ。僕が愛用してきた第2版にはたしかこんな記述はなかったような......。というわけで、1位に並ぶ方法は判ったものの、ySas氏に敬意を表して、このままにしておきます。もしも、もっと短くできる方法が判れば容赦はしませんが、多分ないよね?
おまけ
これだけだと、普通の人にはおもしろくもなんともないので、PostScriptの性質を活かして印刷したりいろいろできるバージョンを用意してみました。
%!PS-Adobe-2.0 EPSF-1.2
%%Creator:Makoto Kaga
%%Title:FizzBuzz
%%BoundingBox:10 19 52 726
/Courier findfont 6.5 scalefont setfont
0 setgray
10 719 moveto
1 1 100 { dup 15 mod 0 eq {(FizzBuzz)} {dup 3 mod 0 eq {(Fizz)} {dup 5 mod 0 eq {(Buzz)}{4 string cvs} ifelse} ifelse } ifelse dup = gsave show grestore 0 -7 rmoveto clear } for
showpage
%!PS-Adobe-2.0 EPSF-1.2
%%Creator:Makoto Kaga
%%Title:FizzBuzz
%%BoundingBox:10 20 530 770
/Helvetica findfont 20 scalefont setfont
0 setgray
(hello)
1 1 100 { dup 1 sub dup 4 mod 140 mul 10 add exch 4 idiv 30 mul 750 exch sub moveto dup 15 mod 0 eq {(FizzBuzz)} {dup 3 mod 0 eq {(Fizz)} {dup 5 mod 0 eq {(Buzz)}{4 string cvs} ifelse} ifelse } ifelse show clear } for
showpage
それぞれ、FizzBuzz3.ps、FizzBuzz4.psとしてダウンロードできますが、これを最近のAdobe IllustratorやPhotoshopで開くと結果が得られます。ちなみに、それぞれIllustrator 10で開くとこんな感じです。
昔、僕がPostScriptをhackしてたころのIllustratorやPhotoshopは、Illustrator形式のファイル(中身はPostScriptではありましたが、決まった作法に則っている必要がありました)くらいしか読めなかったのに、ずいぶん進歩したものです。
コメント
凄い面白かったです! PostScriptに興味有りつつ、スルーしてきましたが一気に興味が出てきた。今の仕事に全然関係ないけれどw
投稿者: takio-h | 2007年5月13日 01:13