頭の体操で剰余演算子を使わずにFizzBuzzを解く

会社の先輩エンジニアに、「剰余演算」なしでFizzBuzz書いて!
って何気なく言われたので書いてみました。

ルールのおさらい

  • 入力値は0以上の正の整数
  • 以下のルールで、結果を文字列として出力。
    上から順に優先度が高いルール

    • 入力値が15で割り切れる場合は[FizzBuzz]を出力
    • 入力値が 5で割り切れる場合は[Buzz]を出力
    • 入力値が 3で割り切れる場合は[Fizz]を出力
    • どれにも当てはまらない場合、入力値をそのまま出力

今回はこの基本ルールに、以下のルールを追加します。

・割った余りを求める演算子「%(剰余演算)」を使わない

実装一覧

剰余演算を使った普通の実装

特に言うことはない、いたってシンプルなやつ。

多少関数化したりできますが、概ねこの形になると思います。
テスト用としてListに結果を追加しています。

ふつうのFizzBuzzですね。

public class FizzBuzz {

    public static void main(String[] args) {
        System.out.println(doFizzBuzz(100));
    }

    public static List<String> doFizzBuzz(int fizzBuzz) {

        List<String> retList = new ArrayList<>();
        for (int i = 1; i < fizzBuzz + 1; i++) {
            if (i % 3 == 0 && i % 5 == 0) {
                retList.add("FizzBuzz");
            } else if (i % 5 == 0) {
                retList.add("Buzz");
            } else if (i % 3 == 0) {
                retList.add("Fizz");
            } else {
                retList.add(String.valueOf(i));
            }
        }
        return retList;

    }
}


三項演算子で実装

三項演算子でtrue/falseの結果によって文字列を返す形。
シンプルですが、for文のindex開始が1なのでちょっと違和感ありますね。

public class FizzBuzzDevide {

    public static void main(String[] args) {
        System.out.println(doFizzBuzz(100));
    }

    public static List<String> doFizzBuzz(int execNum) {
        List<String> retList = new ArrayList<>();

        for (int i = 1; i < execNum + 1; i++) {
            String result = fizzBuzz(i);
            retList.add(result);
        }
        return retList;
    }

    private static String fizzBuzz(int i) {
        return isMatch(i, 15) ? "FizzBuzz"
              : isMatch(i, 5) ? "Buzz"
              : isMatch(i, 3) ? "Fizz"
              : String.valueOf(i);
    }

    private static boolean isMatch(int index, int fizzbuzzTerm) {
        return (index / fizzbuzzTerm) * fizzbuzzTerm == index;
    }
}

再帰で実装

最初に配列を用意して、値を取り出していく実装。
用意した配列要素が16なので、繰り返し処理で配列を超えた要素を取得しようとするとArrayIndexOutOfBoundsExceptionが発生するのを期待して再帰します。
さらに、取得時に値が存在するかどうかで出力値を変えています。

仕事としてでこういう実装書いたら、まずレビュー通らないですね。
例外は例外として処理すべきです。

public class FizzBuzzRecursive {

    private static String[] _FizzBuzz = new String[16];

    static {
        _FizzBuzz[3] = "Fizz";
        _FizzBuzz[6] = "Fizz";
        _FizzBuzz[9] = "Fizz";
        _FizzBuzz[12] = "Fizz";
        _FizzBuzz[5] = "Buzz";
        _FizzBuzz[10] = "Buzz";
        _FizzBuzz[15] = "FizzBuzz";
    }

    public static void main(String[] args) {
        System.out.println(doFizzBuzz(100));
    }

    public static List<String> doFizzBuzz(int limit) {
        List<String> retList = new ArrayList<>();

        for (int i = 1; i < limit + 1; i++) {
            String result = fizzBuzzRecursive(i, i);
            retList.add(result);
        }
        return retList;
    }

    private static String fizzBuzzRecursive(int origin, int convert) {
        String fizzbuzz = null;
        try {
            fizzbuzz = getFizzBuzzElement(convert, origin);

        } catch (ArrayIndexOutOfBoundsException e) {
            // 配列要素を超えた場合、15で引いて再帰する事で16以上の数に対応する
            fizzbuzz = fizzBuzzRecursive(origin, convert - 15);
        }
        return fizzbuzz;
    }

    private static String getFizzBuzzElement(int convert, int origin) {
        try {
            Objects.requireNonNull(_FizzBuzz[convert]);

            return _FizzBuzz[convert];

        } catch (NullPointerException e) {

            // 値がnullの場合はFizzBuzzの条件に合わないので数値を返却
            return String.valueOf(origin);
        }
    }
}

一行で実装

三項演算子の応用版。
Stream中に数値を文字列に変換する所は、参考演算子と同じ判定ロジックです。
テストができないので、できれば値を返却する関数を用意したほうが良いですが

「1行」というコンセプトに反するので辞めました。
フォーマッターかけるとたぶん複数行になってしまいます。あしからず。

public class FizzBuzzOneLiner {

    public static void main(String[] args) {
        Stream.iterate(1, i -> ++i).limit(100).map(i -> (i/15*15==i ? "FizzBuzz" : i/5*5==i ? "Buzz" : i/3*3==i ? "Fizz" : i)).forEach(System.out::println);;
    }
}

その他、すごいやつ

for文やwhile文、if文などを禁止しているさらに厳しい条件。
後半異常な出来具合です。発想力が凄い

シェアする

  • このエントリーをはてなブックマークに追加

フォローする