# Create a for loop that iterates up to 100 while outputting "fizz" at multiples of 3, "buzz" at multiples of 5 and "fizzbuzz" at multiples of 3 and 5. > FizzBuzz 是一個經典的編程面試考題。 > 建立一個for迴圈,印出從1~100,若為"3的倍數顯示 Fizz "、若為"5的倍數顯示 Buzz "、若同為3和5的倍數顯示 FizzBuzz" ## 利用餘數的觀念解決問題: > 在算術中,當兩個整數相除的結果不能以整數商表示時,餘數便是其「餘留下的量」。**當餘數為零時,被稱為整除。** > 程式執行時,順序是一行一行下來。所以最嚴格的條件,應該要放在前面優先執行。**當 i 對同為3和5的倍數 取餘數,餘數為零時,則顯示"FizzBuzz"** 應寫在 if-else 的判斷式的前面。 ## 各式各樣的解法: > 題目建議在面試時選擇清楚好理解的方式,程式比較長也沒關係。 ### 較常見的if else: #### 版本 1: ```javascript= const n = 100; for (var i=1; i <= n; i++){ if (i % 15 == 0) console.log("FizzBuzz"); else if (i % 3 == 0) console.log("Fizz"); else if (i % 5 == 0) console.log("Buzz"); else console.log(i); } ``` :sunflower:**程式步驟:** 1. 先令某數為 i 1. 當 i 對 同為3和5的倍數 取餘數,餘數為零時,則顯示"FizzBuzz" 1. 當 i 對 3 取餘數,餘數為零時,則顯示"Fizz" 1. 當 i 對 5 取餘數,餘數為零時,則顯示"Buzz" :::info 答案印出來是對的! 但不建議使用(3X5)15的倍數去解題,因為==3的倍數印Fizz==、==5的倍數印Buzz==,這兩個條件是獨立的,直接相乘的話程式的可讀性會降低。 如果多了其他的條件,就要多寫。(eg. 7的倍數印woof, i%7、i%21、i%35、i%105) ::: #### 版本1 修正: ```javascript= const n = 100; for (var i=1; i <= n; 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); } ``` <hr> #### 版本 2: ```javascript= const n = 100; for(let i = 1; i <= n; i++){ let result = ''; if(i%3 === 0){ result += 'Fizz' } if(i%5 === 0){ result += 'Buzz' } if(i%7 === 0){ result += '777' } //直接加一個if判斷解決版本1的問題 if(result.length >0){ console.log(result); } else { console.log(i); } } ``` 有新增的條件只要多增加一組if判斷就可以,原理是用空的字串 result ,遇到符合條件的就疊加上去。 <hr> <hr> ### 利用三元運算子: ``` condition ? exprIfTrue : exprIfFalse ``` [Ternary(Conditional)operator 三元(條件) 運算子](https://hackmd.io/@pikachuchu/B1S8bNVet) #### 版本1 : ```javascript= for (var i = 1; i <= 100; i++) { str = (i % 5 == 0 && i % 3 == 0) ? "FizzBuzz" : (i % 3 == 0 ? "Fizz" : (i % 5 == 0) ? "Buzz" : i); console.log(str); } ``` :sunflower:**詳細步驟:** 1. 建立一個For迴圈從1數到100 1. 當 i **對 同為3和5的倍數 取餘數**。餘數為0時,執行"FizzBuzz",餘數不為0,執行``(i % 3 == 0 ? "Fizz" : (i % 5 == 0) ? "Buzz" : i)`` 1. 當 i **對 3 取餘數**。餘數為0時,執行`"Fizz"`,餘數不為0,執行``(i % 5 == 0)? "Buzz" : i`` 1. 當 i **對 5 取餘數**,餘數為0時,執行`"Buzz"`,餘數不為0,執行`i`。 <hr> #### 版本2 : ```javascript= for (var i = 1; i <= 100; i++) { var f = i % 3 == 0, b = i % 5 == 0; console.log(f ?( b ? "FizzBuzz" : "Fizz" ):( b ? "Buzz" : i)); } ``` :sunflower:**詳細步驟:** ==當程式跑 i=15 的情況:== 1. 建立一個For迴圈從1數到100 1. `f ?( b ? "FizzBuzz" : "Fizz" ):( b ? "Buzz" : i)`,餘數為0時,執行`( b ? "FizzBuzz" : "Fizz" )`,~~餘數不為0,執行`( b ? "Buzz" : i)`~~。 3. `b ? "FizzBuzz" : "Fizz"`,餘數為0時,執行`"FizzBuzz"`,~~餘數不為0,執行`"Fizz"`。~~ <hr> #### 版本3 : 目前看到最簡潔的寫法 ```javascript= for(i=0;i<100;)console.log((++i%3?'':'Fizz')+(i%5?'':'Buzz')||i) ``` :::info 一般情況下沒問題,但在“嚴格模式”(use strict)下若省去`let`,會跑錯。*ReferenceError: i is not defined* ::: :sunflower:**運用到的觀念:** | Operator | Usage | Description | | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | 增加 (++) | 一元運算子。 將運算元增加1。假如使用在運算元之前 ``(++x)``,運算元會回傳增加1後的值;假如使用在運算元之後。 ``(x++)``, 會回傳運算元加1前的值。 | 假如 x是 3,那 ``++x`` 將把 x 設定為 4 並回傳 4,而 ``x++`` 會回傳 3 , 接著才把 x 設定為 4。 | | OR (ll) | 運算式1 ll 運算式2 | 假如 運算式1 可以被轉換成 true的話,回傳 運算式1; 否則,回傳 運算式2。 因此,**ll在 兩個運算元有任一個是True 時就會回傳 True,否則回傳 false**。 | | Falsy values | ![](https://i.imgur.com/vZKj0mQ.png) | **false values以外的都是truthy values** | :sunflower:**詳細步驟:** ==程式跑第一次 `i=0` 的情況:== **先來處理console.log前段的部分:** 1. `++i%3 ? '' : 'Fizz'` ------------> 使用到三元運算子的觀念 `condition ? value if true : value if false`。 如果為 true執行 `''` ,false執行`Fizz`。 2. `++i%3` ------------> 運用到增加運算符 ++的觀念。 我們第一個執行的i為1 (++i使0變1)。 1%3 會得到 1,運算式會變成 `1 ? '' : 'fizz'`,1為truthy value, ![](https://i.imgur.com/1vZ2tMB.png) 所以會變成`true ? '' : 'fizz'`,最終得出`''`。 3. 整段程式變成 ```javascript= for(let i=0;i<100;) console.log( ( '' ) + ( i%5 ? '' : 'buzz' ) || i ) ``` **接著處理console.log後段的部分:** 1. `i%5 ? '' : 'buzz'` ----------------> 一樣使用到三元運算子的觀念 `condition ? value if true : value if false`。 如果為 true執行 '' ,flase執行buzz。 2. `i%5` 這邊的i帶入1, 1%5 會得到1,運算式會變成 `1 ? '' : 'buzz'`,1為truthy value ![](https://i.imgur.com/ZfXeMBG.png) 所以會變成`true ? '' : 'buzz'`,最終得出`''`。 程式經過了上面兩段步驟被簡化成 ```javascript= console.log( '' + '' || i ) ``` 由於空字串加空字串仍會是空字串,程式會再進一步簡化成 ```javascript= console.log( '' || i ) ``` 我們會運用到OR(||)的觀念,由於空字串是 JavaScript 中的七個falsy values之一,程式會跳過空字串執行下一個運算元(operand),這邊指的是i。 ```javascript= console.log(i) //1 ``` 分開操作看看結果: https://codepen.io/natalia0604/pen/PojqpQP ==當程式跑 `i=14` 的情況:== ```javascript= for(let i=0;i<100;) console.log( ( ++i%3 ? '' : 'fizz' ) + ( i%5 ? '' : 'buzz' ) || i ) ``` * `++i%3` 這邊的i會變成15, 運算式會變成`15 % 3 ? '' : 'fizz'`,`15%3` 會得到0, * `i%5` 這邊的i帶入15, 運算式會變成`15 % 5 ? '' : 'buzz'`,`15%5` 會得到0。 ![](https://i.imgur.com/qVtpJ4e.png) 0為falsy value,所以程式會變成 ```javascript= console.log( 'fizz' + 'buzz' || i ) ``` 再簡化成 ```javascript= console.log( 'fizzbuzz' || i ) ``` 由於非空字符串('fizzbuzz')是 truthy value,OR(||) 將短路並返回 'fizzbuzz' ```javascript= console.log('fizzbuzz') ``` <hr> <hr> ### 搭配forEach ``` javascript= var multipliers = [ [3, "Fizz"], [5, "Buzz"] ] for (let i = 1; i <= 100; i++) { let output = ""; multipliers.forEach(function(item) { if (i % item[0] === 0) { output += item[1]; } }); console.log(output || i); } ``` :sunflower:**詳細步驟:** * `item[0]`指的是multipers的3、5,所以每當`i++`時都會讓 i對3和5取餘數。 * 如果`i%item[0]=== 0`成立就會將`item[1]`的字串(指的是"Fizz"和"Buzz" )加入`output`裡,接著執行 `console.log(output || i)` * 依照OR(||)的規則,`output`不為空字串時,會回傳`console.log(output)`。 **分開操作看看結果:** ==當程式跑 `i=15` 的情況:== ```javascript= var multipliers = [ [3, "Fizz"], [5, "Buzz"] ] for (let i = 15; i <= 15; i++) { let output = ""; multipliers.forEach(function(item) { console.log(item[0]); if (i % item[0] === 0) { output += item[1]; } }); console.log(output || i); } ``` <hr> <hr> ### 搭配map #### 版本1 : ```javascript= [...Array(100).keys()].map(n => ++n).map(n => console.log(`${n % 3 ? '' : 'Fizz'}${n % 5 ? '' : 'Buzz'}` || n)); ``` | Column 1 | Column 2 | Column 3 | | -------- | -------- | -------- | | Template literals (樣板字面值) | ${expression} | 可以放入變數或任何的表達式。 eg.( ${3+4} 、 \${2*(numA + numB)} ) 。 當 \${'one'}${'two'} 可以合併字串,'onetwo'。| ```javascript= // ${}${} let a = 'apple'; let b = 'banana'; console.log(`${a}${b}`); //"applebanana" ``` :sunflower:**詳細步驟:** ==簡化成跑到15的版本== ```javascript= [...Array(15).keys()].map(n => ++n).map(n => console.log(`${n % 3 ? '' : 'Fizz'}${n % 5 ? '' : 'Buzz'}` || n)); ``` * `[...Array(15).keys()].map(n => ++n)` ------------> 會得到一個從1-15的陣[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15] * 用map()去遍歷array裡的每一個元素,跑console.log裡的判斷式。 ```javascript= // 當n=1 console.log(`${n % 3 ? '' : 'Fizz'}${n % 5 ? '' : 'Buzz'}` || n) 會簡化成 console.log(${''} || n) 再簡化成 console.log(n) //當n=3 console.log(`${n % 3 ? '' : 'Fizz'}${n % 5 ? '' : 'Buzz'}` || n) 會簡化成 console.log(${'Fizz'}${''} || n) 再簡化成 console.log('Fizz') //當n=15 console.log(`${n % 3 ? '' : 'Fizz'}${n % 5 ? '' : 'Buzz'}` || n) 會簡化成 console.log(${'Fizz'}${'Buzz'} || n) 再簡化成 console.log('FizzBuzz') ``` <hr> #### 版本 2: ```javascript= a=[,,,'Fizz',,'Buzz'] Array.from(' '.repeat(101)).map((b,i)=>i&&console.log( a.map((c,j)=>i%j?'':c).join('')||i )) ``` :sunflower:**詳細步驟:** 先看前半段 ```javascript= a= [ , , ,'Fizz', ,'Buzz'] index[0,1,2, 3 ,4, 5 ] Array.from() 可以產生array的方法 Array.from(' '.repeat(101)) 會印出101個空字串的array Array.from(' '.repeat(101)).map((b,i)=>i 會印出0-100的array ``` 再來看console.log() ==當程式跑 `i=15` 的情況:== ```javascript= // 當 i=15 a.map((value,index)=>i%index?'':value).join('')||i a.map(("",0)=>15%0?'':"") //'' a.map(("",1)=>15%1?'':"") //'' a.map(("",2)=>15%2?'':"") //'' a.map(('Fizz',3)=>15%3?'':'Fizz') //'Fizz' a.map(("",4)=>15%4?'':"") //'' a.map(('Buzz',5)=>15%5?'':'Buzz') //'Buzz' join('') ------------> 'FizzBuzz' a.map('FizzBuzz'||i) //'FizzBuzz' ``` <hr> #### 版本 3: ```javascript= console.log(Array(100).fill(1).map(function(val, index) { index++; if (index % 15 == 0){return "FizzBuzz";} if (index % 3 == 0){return "Fizz";} if (index % 5 == 0){return "Buzz";} return index; }).join('\n')) ``` :sunflower:**詳細步驟:** ==簡化成跑到15的版本== * `Array(15).fill(1)` -----------------> 會得到[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] * `Array(15).fill(1).map()` ``` [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] index[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 ] ``` ```javascript= function(val, index) { index++; if (index % 3 == 0 && index % 5 == 0){return "FizzBuzz";} if (index % 3 == 0){return "Fizz";} if (index % 5 == 0){return "Buzz";} return index; }).join('\n')) ``` ![](https://i.imgur.com/kiibpib.png) [What is Array.apply actually doing](https://stackoverflow.com/questions/25512771/what-is-array-apply-actually-doing) [codepen](https://codepen.io/phoebe4561/pen/yLXLQEM?editors=0012) ## 小結: 可以從 考慮**拓展性、效率**的角度去進化你的fizzbuzz程式。 還有很多有創意,上面沒提到的解法,可以點進去慢慢看 [FizzBuzz JavaScript solution](https://gist.github.com/jaysonrowe/1592432) ## 參考資料: [JavaScript — Breaking Down The Shortest Possible FizzBuzz Answer](https://codeburst.io/javascript-breaking-down-the-shortest-possible-fizzbuzz-answer-94a0ad9d128a) [簡單的 FizzBuzz 藏有 深度(google 面試題)](https://bear-1111.medium.com/%E7%B0%A1%E5%96%AE%E7%9A%84-fizzbuzz-%E8%97%8F%E6%9C%89-%E6%B7%B1%E5%BA%A6-google-%E9%9D%A2%E8%A9%A6%E9%A1%8C-f5e66e3a97be) https://stackoverflow.com/questions/25512771/what-is-array-apply-actually-doing