Java 30byte FizzBuzz

なっちゃん(以下な)「なんかさー。タイトルが無理すぎない?」
ぎぃくん(以下ぎ)「まあ流行りだからね」
せっちゃん(以下せ)「流行りって言ってもFizzBuzzを30バイトで - Togetterが1/24だし、1週間経ってるけど。どちらかと言えばオワコン?」
な「まあまあ」
ぎ「とりあえず、どのぐらい無理っぽいか見てみるかい?」

普通のFizzBuzz

せ「じゃぁまあ普通のFizzBuzzをみてみましょ。」
な「FizzBuzz Javaでググって…これかな? Fizz Buzz - Semicolonless Java

public class FizzBuzz {
        public static void main(String[] args) {
                for (int i : new int[] { 0 }) {
                        while (++i <= 100) {
                                if (i % 15 == 0) {
                                        if (null == System.out.printf("FizzBuzz\n")) {}
                                } else if (i % 3 == 0) {
                                        if (null == System.out.printf("Fizz\n")) {}
                                } else if (i % 5 == 0) {
                                        if (null == System.out.printf("Buzz\n")) {}
                                } else {
                                        if (null == System.out.printf("%d\n", i)) {}
                                }
                        }
                }
        }
}

ぎ「違う!それSemicolonlessJavaじゃねぇか!」
な「えー。1ページ目に出てきたよ?」
せ「それは世も末ね。このへんがわかりやすいかしらhttp://homepage2.nifty.com/igat/igapyon/diary/2007/ig070702.html

/**
 * FizzBuzz: イギリスの学校の子供たちの遊び
 * 
 * 1から100までの数をプリントする。<br>
 * ・3の倍数のときは数の代わりに「Fizz」とプリントする。<br>
 * ・5の倍数のときは数の代わりに「Buzz」とプリントする。<br>
 * ・3と5両方の倍数の場合には数の代わりに「FizzBuzz」とプリントする。
 */
public class FizzBuzzNormal04 {
        /**
         * エントリポイント。
         * 
         * @param args
         *            コマンドライン引数。このプログラム内では利用されない。
         */
        public static void main(final String[] args) {
                // TODO:一部共通化を実施しました。
                // TODO:マジックナンバーは追放されていません。
                for (int number = 1; number <= 100; number++) {
                        // 3の倍数であるかどうか。
                        final boolean isMultipleOf3 = (number % 3 == 0);
                        // 5の倍数であるかどうか。
                        final boolean isMultipleOf5 = (number % 5 == 0);

                        final StringBuffer bufMsg = new StringBuffer();
                        if (isMultipleOf3 && isMultipleOf5) {
                                // 3と5両方の倍数の場合には「FizzBuzz」とセットする。
                                bufMsg.append("FizzBuzz");
                        } else if (isMultipleOf3) {
                                // 3の倍数のときは数の代わりに「Fizz」とセットする。
                                bufMsg.append("Fizz");
                        } else if (isMultipleOf5) {
                                // 5の倍数のときは「Buzz」とセットする。
                                bufMsg.append("Buzz");
                        } else {
                                // 数をセットする。
                                bufMsg.append(number);
                        }

                        // 生成された文字列をプリントする。
                        System.out.println(bufMsg.toString());
                }
        }
}

必要な文字

な「public class F{}ってのは外せないよね?」
せ「標準出力に出すならSystem.out.print()も外せないわね」
な「あとpublic static void main(String[] a){}かな」
ぎ「文字列リテラルの"Fizz"と"Buzz"も必要だな」
せ「16+18+37+6+6=87なんだけど」
な「http://www.shinh.org/p.rb?FizzBuzz#JavaだとJavaは97文字が最短記録みたい」


ぎ「これはショートコーディング(あるいはコードゴルフ)と呼ばれるジャンルなんだ。興味があればhttp://www.amazon.co.jp/dp/4839925232を読むといい」
せ「えらくマニアックな本もあったものね」
な「お仕事とかじゃまったく使えないテクニック満載ね」


ぎ「例えばmainメソッドは書かないのがJavaのショートコーディングの基本だ」
な「なになに…。static初期化ブロックにコードを書くって?」

public class F{
  static{
    System.out.println("HelloWorld!");
  }
}

せ「コンパイルはできるけど、実行はできないんじゃない?」
ぎ「たしかに例外は出る」

HelloWorld!
Exception in thread "main" java.lang.NoSuchMethodError: main

な「例外の前にHelloWorld!ってでてるね」
ぎ「そう。クラスがロード時の実行順番を思い返そう。継承階層のことは今回はおいておくとして、static初期化ブロックやstaticフィールドの初期化がまず行われる。」
せ「その時点でstatic初期化ブロックが実行されて、それからmainメソッドがないって言われるわけね*1
な「37文字のmainメソッドの宣言が8文字に短縮されたよ」
ぎ「ついでにclass宣言のpublicも必要ない」

ショートコーディングに挑戦

ぎ「これでコードを書いてみようか」

class F{
  static{
    for(int i=1;i<101;i++){
      System.out.println((i%15==0)?"FizzBuzz":(i%3==0)?"Fizz":(i%5==0)?"Buzz":i);
    }
  }
}

せ「インデントと改行を除けば116文字ね」
な「"FizzBuzz"ってところがなんとかならないのかしら」
ぎ「ここが地味に難しいポイントだな。例えば」

System.out.println((i%3==0)?"Fizz":""+((i%5==0)?"Buzz":i));

ぎ「ってな感じにして前半と後半を文字列連結にすると」

1
2
Fizz3
4
Buzz
Fizz6
7
8
Fizz9
Buzz
11
Fizz12
13
14
FizzBuzz

な「あらら。Fizzの後ろに余計なのがついちゃうのね」
せ「iの所にまた3項演算子いれたら?」

System.out.println((i%3==0)?"Fizz":""+((i%5==0)?"Buzz":(i%3==0)?"":i));

な「この部分で71文字。最初のが75文字だから4文字しか減らないよ」
ぎ「3項演算子の条件部分を==0じゃなくて>0にして、thenとelseを逆にしてみよう」

System.out.println((i%3==0)?"Fizz":""+((i%5>0)?(i%3>0)?i:"":"Buzz"));

ぎ「前半部分を>0にするとうまく動かなくなる。演算子の優先順位がややこしいところだな」
な「これで2文字縮まったね!これで110文字!」
せ「ちょっとまって。Javaの最短記録の97文字まであと13文字もあるのよ?」
な「これじゃ30文字とかどうあがいても無理じゃない?」


ぎ「30文字は無理だけど、30byteなら手はあるね」
な、せ「ナ、ナンダッテー!」

1文字 != 8bit

ぎ「ASCIIコードって7bitなのは知ってる?」
な「えっ」
せ「その昔、1byteが7bitのコンピュータがあったとか(参考)」
ぎ「FizzBuzzが110文字で書けるなら、1文字2bitの文字コードで記述すれば220bitで記述できる。byteに直すと28byteになる」
な「無茶言うなあ」
せ「だいたい、1文字2bitじゃ文字が4種類しか扱えないじゃない」
ぎ「まぁ例えばの話だね、何らかのビット列を元にFizzBuzzのプログラムに展開する文字コードがあったとしたらどうだい?」

文字コードを作る

な「そんなこと言っても文字コードなんてどうやって作るのよ?」
ぎ「ふふっ。Javaをあまり甘く見ないほうがいい。そういうことが出来るようになっているんだよ。参考サイトはこれ。http://www.utilz.jp/wiki/Charset
ぎ「今回はこれのTestDecoderに細工をする」

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;

public class TestDecoder extends CharsetDecoder{

    public TestDecoder(Charset cs){
        super(cs, 0.5f, 1.0f);
    }

    public CoderResult decodeLoop(
                    ByteBuffer in, CharBuffer out){
    	out.put("class F{static{for(int i=1;i<101;i++){" +
    			"System.out.println((i%3==0)?\"Fizz\":\"\"+((i%5>0)?(i%3>0)?i:\"\":\"Buzz\"));}}}");
        return CoderResult.UNDERFLOW;
    }
}

な「ちょ…」
せ「あんまりだわ…。恥も外聞もないわね…」
ぎ「0byte FizzBuzzってわけだ」

実行環境

ぎ「じつはこれ、javacに文字コードをなかなか認識させれなくてね」
な「駄目じゃない」
ぎ「しょうがないからtoolkit使ってJava上でjavacを呼び出したんだ」
せ「いずれにせよゴルフ場*2では通用しないわけね」
ぎ「お後がよろしいようで」

*1:コードゴルフのサーバによっては例外が出ても成功とされるケースがある。そうではない場合にはSystem.exit(0);とするなど別の手を考えなくてはならない。イメージとしてはサーバ上で動くJUnitのような自動テストをクリアしさえすればいい。たまにテストに穴があって最適化されてしまうケースがある

*2:コードゴルフを開催しているサーバのこと