FizzBuzz for JavaScript

FizzBuzz for JavaScript

前言

FizzBuzz 其實是一個遊戲,而這個遊戲也被作為一個程式設計面試考題,尤其是 Google、Facebook 等這些大公司都會使用這個 FizzBuzz 來考驗應徵者的基本程式能力,因此這個遊戲也是一個很好的練習題目。

簡單認識一下 FizzBuzz

相信滿多人都聽過 FizzBuzz,就算不知道也沒關係,其實 FizzBuzz 是一個很簡單的數學遊戲,而這個數學遊戲也廣泛被使用在程式設計的面試中,所以如果你是一位求職者,我會非常推薦你要去了解什麼是 FizzBuzz,以及它的規則。

那麼 FizzBuzz 的規則如下:

  1. 如果這個數字可以被 3 整除,則輸出 Fizz
  2. 如果這個數字可以被 5 整除,則輸出 Buzz
  3. 如果這個數字可以被 3 與 5 同時整除,則輸出 FizzBuzz
  4. 如果這個數字都不符合上述條件,則輸出這個數字

所以基於以上規則來講,玩法就會如下:

  1. 一群玩家圍繞起來
  2. 從指定的第一位玩家開始,第一位玩家說 1
  3. 第二位玩家接著說 2
  4. 第三位玩家時,第三位玩家要說 Fizz,而不是 3
  5. 第四位玩家時,第四位玩家要說 4
  6. 第五位玩家時,第五位玩家要說 Buzz,而不是 5

以此類推,當玩家說到 15 時,第十五位玩家要說 FizzBuzz,而不是 15

以上就是 FizzBuzz 的規則。

FizzBuzz for JavaScript

那麼套用到程式語言中,常見的考題不外乎是以下:

「請建立一個 FizzBuzz 函式,當傳入一個數字時,請回傳符合 FizzBuzz 規則的結果,這邊將會傳入 1 到 100 的數字。」

1
2
3
4
FizzBuzz(1); // 1
FizzBuzz(3); // Fizz
FizzBuzz(5); // Buzz
FizzBuzz(15); // FizzBuzz

那麼透過上述考題以及 FizzBuzz 的規則,我們可以分析出幾個步驟:

步驟一:建立 FizzBuzz 函式

由於會傳入 1~100 的數字,代表著我們這邊會需要使用到迴圈,跑 1~100 的數字,因此我們會使用 for 迴圈來處理

1
2
3
4
5
const FizzBuzz = (num) => {
for (let i = 1; i <= num; i++) {
console.log(i);
}
};

FizzBuzz

第一個步驟相對比較單純簡單,只需要建立一個 FizzBuzz 函式,並且使用 for 迴圈跑 1~100 的數字,這樣就完成了第一步驟。

步驟二:判斷是否為 3 的倍數

接著我們要判斷是否為 3 的倍數,當出現可以被 3 整除的倍數時,就要回傳 Fizz,那麼如何判斷是否為 3 的倍數呢?這邊就要使用到餘數運算子 %

那麼什麼是 % 呢?% 叫做餘數運算子,使用方式很簡單,只需要 a % b,當 a 能被 b 整除時,則會回傳 0,否則會回傳餘數,舉例來講:

1
2
3
4
const a = 5;
const b = 3;

console.log(a % b); // 2

餘數運算子

那麼為什麼會回傳 2 呢?你可以想像成有五顆蘋果,然後要分給 3 個人,一開始一人一顆,接下來會剩下兩顆無法分,因此這兩顆就是餘數。

如果以計算公式來講,通常會區分成兩個部分,首先要先計算商數,然後再計算餘數:

  • 商數:5/3 = 1.6666666666666667,這邊會取整數部分,也就是 1,代表蘋果一個人只能拿一顆
  • 餘數:5 - 3 x 1 = 2,這邊就是餘數,也就是剩下的蘋果

了解餘數的計算方式之後,讓我們回到 Fizz 的判斷上,當這個數字可以被 3 整除時,就要回傳 Fizz,這邊就要使用到餘數運算子 % 來判斷

1
2
3
4
5
6
7
8
9
const FizzBuzz = (num) => {
for (let i = 1; i <= num; i++) {
if (i % 3 === 0) {
console.log('Fizz');
} else {
console.log(i);
}
}
};

FizzBuzz

這時候有些人可能會寫一個錯誤判斷,也就是使用了 / 運算子來判斷

1
2
3
4
5
6
7
8
9
const FizzBuzz = (num) => {
for (let i = 1; i <= num; i++) {
if (i / 3 === 1) {
console.log('Fizz');
} else {
console.log(i);
}
}
};

FizzBuzz

當你傳入 3 時,你會發現結果是 Fizz,感覺似乎是正確的,但是當你傳入 6 時,你會發現還是出現了 6 而不是 Fizz,這就不符合 FizzBuzz 的規則,也就是可以被 3 整除時就要回傳 Fizz

步驟三:判斷是否為 5 的倍數

接著我們要判斷是否為 5 的倍數,當出現可以被 5 整除的倍數時,就要回傳 Buzz,觀念與前面的 Fizz 一樣,只是改成 5 而已

1
2
3
4
5
6
7
8
9
10
11
const FizzBuzz = (num) => {
for (let i = 1; i <= num; i++) {
if (i % 3 === 0) {
console.log('Fizz');
} else if (i % 5 === 0) {
console.log('Buzz');
} else {
console.log(i);
}
}
};

FizzBuzz

步驟四:判斷是否為 FizzBuzz

最後一個條件就是當這個數字可以被 35 同時整除時,就要回傳 FizzBuzz

這邊其實會牽扯到一個觀念,也就是數學的最小公倍數,而最小公倍數就是兩個數字的倍數中最小的那個數字,有點難懂吧?讓我來舉例一下…

舉例來講,有兩個數字 35,那麼它們的最小公倍數就是 15,為什麼呢?讓我們看一下彼此的倍數:

  • 3 的倍數:3, 6, 9, 12, 15, 18, 21, 24, 27, 30
  • 5 的倍數:5, 10, 15, 20, 25, 30

反之,如果今天是 74 的話,那麼結果就會是 28,因為…

  • 4 的倍數:4, 8, 12, 16, 20, 24, 28
  • 7 的倍數:7, 14, 21, 28

由此可知,我們可以看到 47 的最小公倍數就是 28

理解最小公倍數的部分後,讓我們拉回到 FizzBuzz 的判斷上,當這個數字可以被 35 同時整除時,就要回傳 FizzBuzz,基本上在程式上就會使用到 && 邏輯運算子,也就是 AND 運算子,當兩個條件都符合時,才會執行。

有些人可能會躍躍欲試寫成了以下這種版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
const FizzBuzz = (num) => {
for (let i = 1; i <= num; i++) {
if (i % 3 === 0) {
console.log('Fizz');
} else if (i % 5 === 0) {
console.log('Buzz');
} else if (i % 3 === 0 && i % 5 === 0) {
console.log('FizzBuzz');
} else {
console.log(i);
}
}
};

FizzBuzz

雖然看似沒有錯,但其實這樣是有錯誤的,因為當你傳入 15 時,你會發現結果是 Fizz,而不是 FizzBuzz,這是因為程式是從上往下判斷,當 15 能被 3 整除時,就會回傳 Fizz,而不會再往下判斷是否為 FizzBuzz,所以正確的寫法是要先判斷 FizzBuzz,再判斷 FizzBuzz

1
2
3
4
5
6
7
8
9
10
11
12
13
const FizzBuzz = (num) => {
for (let i = 1; i <= num; i++) {
if (i % 3 === 0 && i % 5 === 0){
console.log('FizzBuzz');
} else if (i % 3 === 0) {
console.log('Fizz');
} else if (i % 5 === 0) {
console.log('Buzz');
} else {
console.log(i);
}
}
};

FizzBuzz

這樣子就會是一個正確的 FizzBuzz 函式了。

TDD?

這邊突然插入一個 TDD 大家應該會感到混亂(笑)

首先 TDD 是什麼?TDD 全名是 Test-Driven Development,中文翻譯為「測試驅動開發」,簡單來講就是先寫測試,再寫程式碼,那麼為什麼突然提到這個呢?因為 FizzBuzz 就非常適合用來練習 TDD,當你寫完 FizzBuzz 函式後,你可以透過 TDD 來驗證你的程式碼是否正確,這樣子就可以確保你的程式碼是正確的。

除此之外,為了讓程式碼可以「被測試」,所以你的程式碼就必須要寫成可以被測試的程式碼,並且要符合單一職責原則,這樣子才能夠讓程式碼更加容易被測試。

TDD 練習

底下是一個最簡單的修改方式,也就是將結果改回傳陣列,並將結果都推到陣列中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const FizzBuzz = (num) => {
const result = [];

for (let i = 1; i <= num; i++) {
if (i % 3 === 0 && i % 5 === 0){
result.push('FizzBuzz');
} else if (i % 3 === 0) {
result.push('Fizz')
} else if (i % 5 === 0) {
result.push('Buzz')
} else {
result.push(i);
}
}

return result;
}

但這一段程式碼再修改上其實並不容易,所以我們可以更抽象化一點,也就是將判斷的部分抽出來並優化不使用 else if,這樣子就可以讓程式碼更加容易被測試:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const FizzBuzz = (num) => {
const result = [];

for (let i = 1; i <= num; i++) {
result.push(checkFizzBuzz(i));
}

return result;
}

const checkFizzBuzz = (i) => {
let output = '';
if(i % 3 === 0) output += 'Fizz';
if(i % 5 === 0) output += 'Buzz';
return output || i;
}

還有呢?字串可不可以也抽出來?當然可以:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const FIZZBUZZ_STRING = {
Fizz: 'Fizz',
Buzz: 'Buzz',
}

const FizzBuzz = (num) => {
const result = [];

for (let i = 1; i <= num; i++) {
result.push(checkFizzBuzz(i));
}

return result;
}

const checkFizzBuzz = (i) => {
let output = '';
if(i % 3 === 0) output += FIZZBUZZ_STRING.Fizz;
if(i % 5 === 0) output += FIZZBUZZ_STRING.Buzz;
return output || i;
}

那麼透過以上範例可以看到光 FizzBuzz 這個簡單題目就可以練到滿多東西的,其中包含了:

  • 基本程式碼認知(如:if 判斷式、for 迴圈、function 宣告、return 回傳、console.log 輸出、&& 邏輯運算子、% 餘數運算子等)
  • 單元測試(TDD)
  • 重構程式碼
  • 抽象化程式碼
  • 單一職責原則
  • 了解最小公倍數觀念

透過上面我們就可以知道,為什麼 FizzBuzz 這個簡單的題目可以被廣泛使用在面試中,因為它可以讓面試者了解到面試者的基本程式能力,以及面試者是否有能力將程式碼寫得更加優化哩~

Liker 讚賞

這篇文章如果對你有幫助,你可以花 30 秒登入 LikeCoin 並點擊下方拍手按鈕(最多五下)免費支持與牡蠣鼓勵我。
或者你可以也可以請我「喝一杯咖啡(Donate)」。

Buy Me A Coffee Buy Me A Coffee

Google AD

撰寫一篇文章其實真的很花時間,如果你願意「關閉 Adblock (廣告阻擋器)」來支持我的話,我會非常感謝你 ヽ(・∀・)ノ