js で Fizz Buzz

**Fizz Buzz** も知らないプログラマーなんて... みたいな記事を読んだ。

知らんかったので、やる。

# Fizz Buzz ？

> 最初のプレイヤーは「1」と数字を発言する。次のプレイヤーは直前のプレイヤーの次の数字を発言していく。ただし、3で割り切れる場合は「Fizz」（Bizz Buzzの場合は「Bizz」）、5で割り切れる場合は「Buzz」、両者で割り切れる場合（すなわち15で割り切れる場合）は「Fizz Buzz」（Bizz Buzzの場合は「Bizz Buzz」）を数の代わりに発言しなければならない。発言を間違えた者や、ためらった者は脱落となる。

> このゲームをコンピュータ画面に表示させるプログラムとして作成させることで、コードが書けないプログラマ志願者を見分ける手法をJeff AtwoodがFizzBuzz問題 (FizzBuzz Question) として提唱した。

出典: [Fizz Buzz - Wikipedia](https://ja.wikipedia.org/wiki/Fizz_Buzz) 

なるほど。

# JavaScript で実装

## ❶ 配列を作るパターン

- オーソドックスに「連番の配列」を用意して評価。

```js
function fizzBuzz(max) {
  return Array(max).fill().map((val, idx) => {
    const num = idx + 1;
    if (num % 15 === 0) return 'FizzBuzz';
    if (num % 3 === 0) return 'Fizz';
    if (num % 5 === 0) return 'Buzz';
    return num;
  });
}

console.log(fizzBuzz(30));

// 結果:
// [1, 2, "Fizz", 4, "Buzz", "Fizz", 7, 8, "Fizz", "Buzz", 11, "Fizz", 13, 14, "FizzBuzz", 16, 17, "Fizz", 19, "Buzz", "Fizz", 22, 23, "Fizz", "Buzz", 26, "Fizz", 28, 29, "FizzBuzz"]
```

### (補足) 連番配列の作り方は、どれがいい？

計測した中では、`Array(arrayLength).fill().map((val, idx) => idx)` が良さそう。

```js
/**
 * 関数を指定回数実行し、平均実行時間を取得する
 * 
 * @param max 試行回数
 * @param func 実行する関数
 * @returns 平均実行時間 (ms)
 */
function getAvgRunTime(max, func) {
    let count = 0
    let timeArr = [];
    while (count < max) {
        let start = performance.now();
        let result = func();
        let end = performance.now();
        timeArr.push(end - start);
        count++;
    }
    return timeArr.reduce((acc, cur) => acc + cur) / timeArr.length;
}

// いろいろな「連番の配列を作る方法」を各 30 回実行し、それぞれの平均実行時間を出力
console.log(getAvgRunTime(30, () => Array(1000000).fill().map((val, idx) => idx) ));
console.log(getAvgRunTime(30, () => [...Array(1000000)].map((val, idx) => idx) ));
console.log(getAvgRunTime(30, () => Array.from(Array(1000000).keys()) ));
console.log(getAvgRunTime(30, () => [...Array(1000000).keys()] ));
console.log(getAvgRunTime(30, () => Array.from({length: 1000000}, (val, idx) => idx) ));

// apply 関数を使う方法は、生成する要素数が多いと Maximum call stack size exceeded などで失敗するので割愛
// console.log(getAvgRunTime(30, () => Array.apply(null, Array(1000000)).map((val, idx) => idx) ));
```

| 連番配列の生成方法                                     | Chrome v84 | Firefox v79 |
| :-------------------------------------------------- | :--------- | :---------- |
| `Array(1000000).fill().map((val, idx) => idx)`      | 19 ms 👑   | 19 ms       |
| `[...Array(1000000)].map((val, idx) => idx)`        | 19 ms 👑   | 22 ms       |
| `Array.from(Array(1000000).keys())`                 | 28 ms      | 14 ms       |
| `[...Array(1000000).keys()]`                        | 38 ms      | 13 ms 👑    |
| `Array.from({length: 1000000}, (val, idx) => idx)`  | 53 ms      | 15 ms       |

## ❷ ジェネレーター関数を使うパターン

- メモリを無駄に使わないように。

```js
function getFizzBuzzAnswer(num) {
    if (num % 15 === 0) return 'FizzBuzz';
    if (num % 3 === 0) return 'Fizz';
    if (num % 5 === 0) return 'Buzz';
    return num;
}

function* makeFizzBuzzIterator(start = 1, end = Infinity) {
    let num = start;
    while (num < end) {
        yield getFizzBuzzAnswer(num);
        num++;
    }
    return getFizzBuzzAnswer(num);
}

let iterator = makeFizzBuzzIterator(1, 30);

while (true) {
    let result = iterator.next();
    console.log(result.value);
    if (result.done) break;
}
```