星座計算機

題目來源: Alpha Camp 學期 2-1 工作坊
在學習 function 的過程中,對於如何設計和應用只有模糊的概念。星座計算機是個規模較小的應用題,可以在不長的流程中清楚看出 fuction 的運作時機和傳回值的流向。透過 function 的使用,可以讓主程式碼盡可能地保持較乾淨的狀態。

解題工具

  1. if statement
  2. function
  3. for 迭代
  4. String.prototype.split(): 用特定字符將字串切割,並回傳切割後陣列。
  5. Number(): 將 () 中參數型別轉換為數字。

題目要求

基本功能

  • 給定一個日期(格式為 4/1),算出此日期對應星座。
  • 用 function 來創建資料。
  • 用 function 來簡化。
  • (自己新增) 判斷輸入日期是否有誤(ex. 4/35)。

課後挑戰–例外處理

摩羯座(12/21 - 1/19) 因為跨年度,和其他星座不同。試著分別用以下三種方式解決例外狀況。

  1. 專用判斷式 (大於1221 或 小於 119)
  2. 排除法 (如果都沒找到,就是魔羯)
  3. 改物件 (12/21-12/31 、 1/1-1/19)

解題過程

原本看到題目的直覺反應是:

  1. 把使用者輸入的生日拆解成 “月” 和 “日” 兩個變數。
  2. 把星座區間分成前後兩半(前月中到月底 + 後月初到月中)。
  3. 用雙層 for 迴圈迭代 “月” 和 “日” ,將星座前、後兩半分別檢查一次,找出生日符合的區間。

整體流程感覺很繁瑣……這時,助教提出一個建議: 將所有日期(月+日)整個數值化,讓星座區間的前後半合併為一個數值的區間 (如天秤 9/23-10/22 轉換為 923-1022的數字區間)。這樣就方便很多了,也解決了跨月份造成的雙層迴圈問題。

拆解流程

  • 先思考整體大方向,寫出流程 1 ~ 5 大項。
  • 防呆措施: 加入 “判斷日期是否存在?” 功能。(加入流程3-1)
  • 如果比較 “月” 再比較 “日” ,判斷流程較繁複,所以轉換日期方便比較。 (加入流程3-2)
  • 依照題意用 function 簡化,預計分別為 1 、 3-1 、 3-2 設計 function 。
  1. 建立星座資料
  2. 使用者輸入生日
  3. 拆解日期
    3-1. 判斷日期是否存在?
    3-2. 轉換日期以簡化判斷星座過程
  4. 判斷星座
  5. 輸出星座

解題開始

在進行解題時,寫出上述運作流程後,我會先進行一次反向思考。

  1. 星座判斷流程需要用到數值化後的日期 ➜ 從 function 日期數值化取得。
  2. 判斷日期是否存在 及 function 日期數值化需要用「月」、「日」來轉換 ➜ 建立星座資料、使用者輸入資料都要將「月」、「日」拆開。
  3. 依照以上所需來設計變數代入 function 及其他解題流程裡。

function 設計

經過前面的流程拆解,可以開始設計 function 了。
1. 日期數值化(將 9/23 轉換為 923)
回傳值為: 將傳入的參數 m(月) 乘 100 再加 d(日)

1
2
3
4
// convert dateValue = month * 100 + date
function dateValue(m, d) {
return m * 100 + d
}

2. 建立星座資料
日期數值化需要分別用到月和日,因此在設計 function 時,需要的參數有: 星座名、起始月、起始日、結束月、結束日。

1
2
3
4
5
6
7
8
// create Object for zodiac
function createZodiac(name, startMonth, startDay, endMonth, endDay) {
return {
name: name,
startDate: dateValue(startMonth, startDay),
endDate: dateValue(endMonth, endDay)
}
}

3. 判斷日期是否存在?
判斷標準為: 2月最多29天,2月外小月最多30天,大月最多31天。
功能: 日期存在則回傳日期,日期不存在則印出日期不存在。

1
2
3
4
5
6
7
8
9
10
11
12
function dateExist(m, d) {
// set date exist range
const Feb = (d <= 29)
const bigMonth = ((m <= 7) && (m % 2 !== 0) || (m > 7) && (m % 2 === 0)) && (d <= 31)
const smallMonth = (m !== 2) && ((m <= 7) && (m % 2 === 0) || (m > 7) && (m % 2 !== 0)) && (d <= 30)
// start
if ((d >= 1) && (Feb || bigMonth || smallMonth)) {
return dateValue(m, d)
} else {
console.log('生日不存在')
}
}

三種方式共同流程

1. 建立星座資料
方式一、方式二都是將摩羯座當例外處理,因此這邊先不建立摩羯座資料。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// place to store info from function 'createZodiac'
const zodiac = []
// use 'createZodiac' push into zodiac
zodiac.push(createZodiac('牡羊', 3, 21, 4, 19))
zodiac.push(createZodiac('金牛', 4, 20, 5, 20))
zodiac.push(createZodiac('雙子', 5, 21, 6, 20))
zodiac.push(createZodiac('巨蟹', 6, 21, 7, 22))
zodiac.push(createZodiac('獅子', 7, 23, 8, 22))
zodiac.push(createZodiac('處女', 8, 23, 9, 22))
zodiac.push(createZodiac('天秤', 9, 23, 10, 22))
zodiac.push(createZodiac('天蠍', 10, 23, 11, 21))
zodiac.push(createZodiac('射手', 11, 22, 12, 21))
zodiac.push(createZodiac('水瓶', 1, 20, 2, 18))
zodiac.push(createZodiac('雙魚', 2, 19, 3, 20))

2. 使用者輸入生日

1
2
// user input
const birthday = prompt('請輸入生日(格式:4/1): ')

3. 拆解日期

1
2
3
4
const birthdayArray = birthday.split('/')
const birthdayMonth = Number(birthdayArray[0])
const birthdayDay = Number(birthdayArray[1])
const birthdayValue = dateExist(birthdayMonth, birthdayDay)

以上部分完成後,就可以依照三種方式分別進行判斷星座 & 輸出結果了(方式一方式二方式三)。

方式一

說明: 幫摩羯座設專用判斷式 (大於1221 或 小於 119)
先用專用判斷式把摩羯座篩選出來,其他的則丟進迴圈裡迭代,找出符合的結果。

1
2
3
4
5
6
7
8
9
10
11
if ((birthdayValue <= 119) || (birthdayValue >= 1221)) {
console.log('你是魔羯座')
} else {
for (i = 0; i < zodiac.length; i++) {
const startValue = zodiac[i].startDate
const endValue = zodiac[i].endDate
if ((birthdayValue >= startValue) &&(birthdayValue <= endValue)) {
console.log(`你是${zodiac[i].name}座`)
}
}
}

(回到共同流程)

方式二

說明: 排除法 (如果都沒找到,就是魔羯)
這個方法讓我卡最久……我的邏輯是: 如果迴圈有找到符合的結果,則印出結果; 如果沒有,則印出 ‘你是摩羯座’。
這種方法忽略了生日不存在的狀況,導致如果生日不存在,會同時印出 ‘生日不存在’ 及 ‘你是摩羯座’。
最後才想到加個 “如果 birthdayValue 有值” 的前提,成功跑出我要的結果。

1
2
3
4
5
6
7
8
9
10
11
12
13
const answer = []
for (i = 0; i < zodiac.length; i++) {
if ((birthdayValue >= zodiac[i].startDate) && (birthdayValue <= zodiac[i].endDate)) {
answer.push(zodiac[i].name)
}
}
if (birthdayValue) { // 如果 birthdayValue 有值
if (answer.length > 0) {
console.log(`你是${answer[0]}座`)
} else {
console.log('你是摩羯座')
}
}

(回到共同流程)

方式三

方式說明:改物件 (12/21-12/31 、 1/1-1/19)
須把摩羯座範圍拆開,並另外放進 zodiac 裡。

1
2
3
4
5
6
7
8
9
10
11
12
// 把摩羯座拆開放進 zodiac 裡
zodiac.push(createZodiac('魔羯', 12, 22, 12, 31))
zodiac.push(createZodiac('魔羯', 1, 1, 1, 19))

for (let i = 0; i < zodiac.length; i++) {
const startValue = zodiac[i].startDate
const endValue = zodiac[i].endDate

if (birthdayValue >= startValue && birthdayValue <= endValue) {
console.log(`你是${zodiac[i].name}座`)
}
}

(回到共同流程)

小結

能成功用三種方法解出來實在很有成就感! 每次看到輸出結果跑出紅字就很頭大,還好他會提醒你到底是哪裡出錯,所以做後還是成功 debug 了!

隨著作業規模變大,開始有感覺到難度。但是比起挫折感,更多的反而是 “我在確實進步” 的興奮!! 讓我在寫部落格的路上越來越有動力!

文章內容如有錯誤,歡迎留言討論!


本 Blog 上的所有文章除特别聲明外,均採用 CC BY-SA 4.0 協議 ,轉載請註明出處!