【TypeScript】FizzBuzzのいろんな書き方
FizzBuzzとは
FizzBuzzは、プログラミングの練習問題として有名で、次のようなルールで1から順番に整数を出力するプログラムです。
・3の倍数ではなく、5の倍数でもないときは整数をそのまま出力する
・3の倍数であり、5の倍数でない時は整数の代わりにFizzと出力する
・3の倍数でなく、5の倍数であるときは整数の代わりにBuzzと出力する
・3の倍数であり、5の倍数でもある時は整数の代わりにFizzBuzzと出力する
例えば、1から15までFizzBuzzを行うと次のように出力されます。
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
今回は、このプログラムのいろいろな書き方を考えてみたいと思います。
パターン1:普通
for(let num = 1; num <= 15; num++) {
if (num % 3 === 0 && num % 5 === 0) console.log('FizzBuzz');
if (num % 5 === 0) console.log('Buzz');
if (num % 3 === 0) console.log('Fizz');
console.log(num.toString());
}
ちなみに、num % 3 === 0 && num % 5 === 0
の部分はnum % 15 === 0
でもOKですが、私はnum % 3 === 0 && num % 5 === 0
の方が良いかと思います。
理由としては、単純に分かりやすいからです。
今回は3
と5
なのでどちらでも良かったですが、例えば問題(要件)に変更が入り6
と8
になったとします。
すると、num % 6 === 0 && num % 8 === 0
かnum % 24 === 0
になりますね。
この24
という数字は、6
と8
の最小公倍数をとっているだけなのですが、パッとみただけではわからないかもしれません。
よって、分かりやすさとメンテナンス性からnum % 3 === 0 && num % 5 === 0
が良いと思います。
パターン2:ループの中の処理を関数に抜き出す
const getFizzBuzz = (num: number): string => {
if (num % 3 === 0 && num % 5 === 0) return 'FizzBuzz';
if (num % 5 === 0) return 'Buzz';
if (num % 3 === 0) return 'Fizz';
return num.toString();
};
for(let num = 1; num <= 15; num++) {
console.log(getFizzBuzz(num));
}
ループの中の処理をgetFizzBuzz
という関数として抜き出しました。
このように大きな処理の一部を関数に抜き出すことは、以下の2つ理由からおすすめします。
1.処理に名前がつく
関数として抜き出すことにより、ループの中の処理にgetFizzBuzz
という名前がつきました。
関数や変数の名前には、中身を説明するという役割があります。
ですので、この関数はnum
という数字を受け取ってFizzBuzz
の結果をget
する関数なんだなあと、中身を詳しく読まなくても把握できます。
2.型情報が付けられる
関数として抜き出すことにより、ループの中の処理に型情報をつけることができました。
このように関数の引数・返り値の方を明記することで、プログラムの安全性が向上するのはもちろんのこと、関数の処理の流れ・データの流れがわかりやすくなります。
パターン3:文字列として連結させる
const getFizzBuzz = (num: number): string => {
let result = '';
if (num % 3 === 0) result += 'Fizz';
if (num % 5 === 0) result += 'Buzz';
return result || num.toString();
};
for (let num = 1; num <= 15; num++) {
console.log(getFizzBuzz(num));
};
空の文字列を作り、そこに条件分岐で当てはまる場合にFizzやBuzzを連結させていくという書き方です。
この書き方の良いところは、以下の2つです。
1.条件分岐が1つ減る
パターン2までは必要だったif(num % 3 === 0 && num % 5 === 0) { return 'FizzBuzz'; }
の部分が省略できるところです。
最初はresult=''
で、3の倍数だったらresult='Fizz'
になり、さらに5の倍数でもあればresult='FizzBuzz'
になるというように、どんどん連結されていくのですね。
2.メンテナンス性に優れている
if(num % 3 === 0 && num % 5 === 0) { return 'FizzBuzz'; }
という文がなくなったことにより、問題(要件)が変わった場合に修正の必要な箇所が格段に減りました。
(例えば、3
と5
という数字が6
と8
に変わり、Fizz
とBuzz
がJizz
とDuzz
に変わった場合を考えてみてください。)
また、return result || num.toString();
という部分で、短絡演算を使用しているところもポイントです。
この部分を短絡演算を使用せずに書くと次のように冗長になります。
const getFizzBuzz = (num: number): string => {
let result = '';
if (num % 3 === 0) result += 'Fizz';
if (num % 5 === 0) result += 'Buzz';
if (result) {
return result;
} else {
return num.toString();
};
};
for (let num = 1; num <= 15; num++) {
console.log(getFizzBuzz(num));
};
パターン4:テンプレートリテラルと三項演算子を使用する
const getFizzBuzz = (num: number): string => {
return `${num % 3 === 0 ? 'Fizz' : ''}${num % 5 === 0 ? 'Buzz' : ''}` || num.toString();
};
for (let num = 1; num <= 15; num++) {
console.log(getFizzBuzz(num));
};
この書き方の良いところは、関数内の処理がたったの1行で書けているところです。
また、テンプレートリテラルの中で三項演算子
を用いているところがポイントです。
三項演算子については、こちらの記事をご覧ください。
まとめ
他にも、以下のように高階関数を使って書くこともできますが、コードが難しくなるだけで他の書き方と比べて特段のメリットがあるわけでもないのでオススメしません。
const fizzBuzz = (num: number, rules: Array<(n: number) => string>): string => {
for (const rule of rules) {
const result = rule(num);
if (result) return result;
}
return num.toString();
};
const rules = [
(n: number) => (n % 15 === 0 ? 'FizzBuzz' : ''),
(n: number) => (n % 3 === 0 ? 'Fizz' : ''),
(n: number) => (n % 5 === 0 ? 'Buzz' : '')
];
for (let num = 1; num <= 15; num++) {
console.log(fizzBuzz(num, rules));
};
実用性があるかどうかはさておき、TypeScriptの勉強になるのでいろいろな書き方を試してみると面白いかもしれません。
他にもっといい書き方があるよという方はぜひ教えてください!