技術とか戦略とか

IT技術者が技術や戦略について書くブログです。

java:実務で使うテクニックでfizzbuzzを解いてみた

10年ほど前に流行ったプログラミングの問題として、fizzbussと呼ばれる問題があります。
この問題は、応募者のプログラミング経験の有無を見極める問題であり、問題の内容は以下の通りです。
・1から100までの数を出力する。
・3の倍数の時は代わりに"fizz"と出力する。
・5の倍数の時は代わりに"buss"と出力する。
・3の倍数かつ5の倍数の時は代わりに"fizzbuss"と出力する。
 
「実務ではfizzbussのようなプログラムを書くことは無いから解けなくても良い」という意見もあるようですが、個人的にはfizzbussで使うテクニックは実務でも使えると思っています。
 
以下では、実務でも使うテクニックを用いてfizzbussを解いてみます。
(言語はjavaです。今回の記事の趣旨上、トリッキーな解答は除外します。)
 
・剰余を用いた一般的な解答パターン
public class Fizzbuss {
    public static void main(String[ ] args) {
        for (int i = 1; i < 101; i++) {
            if (i % (3 * 5) == 0) {
                System.out.println("fizzbuss");
            } else if (i % 3 == 0) {
                System.out.println("fizz");
            } else if (i % 5 == 0) {
                System.out.println("buss");
            } else {
                System.out.println(i);
            }
        }
    }
}
 
恐らく、これが一番自然な解答なのではないかと思います。
剰余を用いて、「n % m == 0」と記述することで、「m毎に何かをする」という記述が可能になります。
性能の観点で、一定のデータが溜まってからまとめてログ出力やデータベース書き込みを行うことがあるのですが、そのような処理を記述する時に使えます。
その他、テストデータを生成する時にも、この書き方を用いると楽になる場合があります。
(例えば、100個データを用意し、その内偶数のデータについては特定のフラグが立ったデータにする等)
 
・同じ分岐の記述を避けるパターン
public class Fizzbuss {
    public static void main(String[ ] args) {
        for (int i = 1; i < 101; i++) {
            String str = "";
            if (i % 3 == 0) {
                str = str + "fizz";
            }
            if (i % 5 == 0) {
                str = str + "buss";
            }
            if (str.equals("")) {
                Integer i2 = i;
                str = i2.toString();
            }
            System.out.println(str);
        }
    }
}
 
先に挙げた解答では「3の倍数の時」「5の倍数の時」の分岐が2回ずつ出現し、若干冗長なので、このように同じ分岐の記述を避ける解答もあります。
同じ分岐を複数記述すると、保守性が低下します。
(例えば、「3の倍数」を「4の倍数」としたい時に、修正漏れが発生する可能性が出てくる)
プログラマーは保守性にも気を配るべきなので、個人的にはこちらの解答の方がよりプログラマーっぽいと思っています。
 
・除算と乗算を組み合わせるパターン
public class Fizzbuss {
    public static void main(String[ ] args) {
        for (int i = 1; i < 101; i++) {
            String str = "";
            if ( (i / 3 * 3) == i ) {
                str = str + "fizz";
            }
            if ( (i / 5 * 5) == i ) {
                str = str + "buss";
            }
            if ( str.equals("") ) {
                Integer i2 = i;
                str = i2.toString();
            }
            System.out.println(str);
        }
    }
}
 
剰余を使わずとも、整数型は小数点以下切り捨てになるということを知っていれば、このような解答を記述することもできます。
除算で小数点以下の切り捨てが発生する場合、同じ数で乗算を行っても元の数に戻らないので、それを利用しています。
除算による切り捨てを実務で用いる場合もあり、例えば100未満の位を切り捨てしたい場合は、「n = n / 100 * 100」と記述したりします。逆に切り上げたい場合は、「n = (n + 99) / 100 * 100」となります。
現代のプログラミング言語では切り捨てや切り上げを行う便利な関数が用意されているのでこのような書き方をすることは少ないですが、このような書き方を知らないと他の人のプログラムを読んでいて困ることがあるので、知っておいた方が無難です。
 
・カウンターを持たせるパターン
public class Fizzbuss {
    public static void main(String[ ] args) {
        int fizzCounter = 0;
        int bussCounter = 0;
        for (int i = 1; i < 101; i++) {
            fizzCounter++;
            bussCounter++;
            String str = "";
            if (fizzCounter == 3) {
                str = str + "fizz";
                fizzCounter = 0;
            }
            if (bussCounter == 5) {
                str = str + "buss";
                bussCounter = 0;
            }
            if (str.equals("")) {
                Integer i2 = i;
                str = i2.toString();
            }
            System.out.println(str);
        }
    }
}
 
fizz用のカウンターとbuss用のカウンターを持たせて、カウンターを上手く制御すると、剰余や切り捨てを利用しなくても解答を記述できます。
実務では、カウンターの数字自体に意味がある場合にこのような書き方をします。
例えば、「ログ1→ログ2→ログ3→ログ1…とローテーションさせたい」という場合に、「ログ1」「ログ2」「ログ3」の数字部分にカウンターの値をそのまま用いることができます。
 
-----------------------
 
追記
「fizzbuss」は「fizzbuzz」のtypoでした。