在前面的學習中,我們已經掌握了將阿拉伯數字轉換為羅馬數字的技巧。今天我們要使用 TDD 來實作反向轉換功能,將羅馬數字轉換回阿拉伯數字 ✨
我們已經完成了羅馬數字轉換器的單向功能:
今天要實作反向轉換,讓轉換器具備雙向功能!
fromRoman()
函數反向轉換需要處理:
特殊減法組合:
建立測試檔案:
// tests/day15/from-roman.test.ts
import { describe, test, expect } from 'vitest'
import { fromRoman } from '../../src/roman/converter'
describe('fromRoman', () => {
test('convertsSingleRomanNumerals', () => {
expect(fromRoman('I')).toBe(1)
})
})
先實作最簡單的版本:
// 更新 src/roman/converter.ts
export function fromRoman(roman: string): number {
return 1
}
擴展測試並重構實作:
// 更新 tests/day15/from-roman.test.ts
describe('fromRoman', () => {
test('convertsSingleRomanNumerals', () => {
expect(fromRoman('I')).toBe(1)
expect(fromRoman('V')).toBe(5)
expect(fromRoman('X')).toBe(10)
})
})
// 更新 src/roman/converter.ts
export function fromRoman(roman: string): number {
const values: Record<string, number> = {
'I': 1, 'V': 5, 'X': 10, 'L': 50,
'C': 100, 'D': 500, 'M': 1000
}
return values[roman] || 0
}
在處理多個符號時,我們需要理解羅馬數字的累加邏輯:
// 更新 tests/day15/from-roman.test.ts
test('convertsMultipleRomanNumeralsWithAddition', () => {
expect(fromRoman('II')).toBe(2)
expect(fromRoman('VI')).toBe(6)
})
// 更新 src/roman/converter.ts
export function fromRoman(roman: string): number {
const values: Record<string, number> = {
'I': 1, 'V': 5, 'X': 10, 'L': 50,
'C': 100, 'D': 500, 'M': 1000
}
let result = 0
for (let i = 0; i < roman.length; i++) {
result += values[roman[i]]
}
return result
}
這個實作只適用於純加法,需要前瞻算法處理減法規則。
在處理 "IV" 時,簡單累加會得到錯誤結果:
// 更新 tests/day15/from-roman.test.ts
test('convertsRomanNumeralsWithSubtraction', () => {
expect(fromRoman('IV')).toBe(4)
expect(fromRoman('IX')).toBe(9)
expect(fromRoman('XL')).toBe(40)
})
// 更新 src/roman/converter.ts
export function fromRoman(roman: string): number {
const values: Record<string, number> = {
'I': 1, 'V': 5, 'X': 10, 'L': 50,
'C': 100, 'D': 500, 'M': 1000
}
let result = 0
for (let i = 0; i < roman.length; i++) {
const current = values[roman[i]]
const next = values[roman[i + 1]]
if (next && current < next) {
result += next - current
i++ // 跳過下一個字符
} else {
result += current
}
}
return result
}
比較相鄰字符大小:如果 current < next
是減法,否則累加。
"MCMXCIV" (1994) 執行過程:
test('convertsComplexRomanNumerals', () => {
expect(fromRoman('XIV')).toBe(14)
expect(fromRoman('MCMXCIV')).toBe(1994)
expect(fromRoman('MMMCMXCIX')).toBe(3999)
})
test('convertsEdgeCases', () => {
expect(fromRoman('CDXLIV')).toBe(444)
expect(fromRoman('CMXC')).toBe(990)
expect(fromRoman('MCDXLIV')).toBe(1444)
})
這些案例測試多個減法組合的正確性。
test('handlesInvalidInput', () => {
expect(() => fromRoman('')).toThrow('Invalid Roman numeral')
expect(() => fromRoman('IIII')).toThrow('Invalid Roman numeral')
expect(() => fromRoman('VX')).toThrow('Invalid Roman numeral')
expect(() => fromRoman('ABCD')).toThrow('Invalid Roman numeral')
})
驗證需求:檢查空字符串、無效字符、重複錯誤及無效減法組合。
// 完整實作 src/roman/converter.ts
export function fromRoman(roman: string): number {
if (!roman) throw new Error('Invalid Roman numeral')
const values: Record<string, number> = {
'I': 1, 'V': 5, 'X': 10, 'L': 50,
'C': 100, 'D': 500, 'M': 1000
}
// 檢查無效字符
for (const char of roman) {
if (!values[char]) throw new Error('Invalid Roman numeral')
}
// 檢查重複規則
const invalidPatterns = ['IIII', 'VV', 'XXXX', 'LL', 'CCCC', 'DD']
for (const pattern of invalidPatterns) {
if (roman.includes(pattern)) {
throw new Error('Invalid Roman numeral')
}
}
// 檢查無效減法組合
const invalidSubtractions = ['VX', 'VL', 'VC', 'VD', 'VM', 'LC', 'LD', 'LM', 'DM']
for (const invalid of invalidSubtractions) {
if (roman.includes(invalid)) {
throw new Error('Invalid Roman numeral')
}
}
let result = 0
for (let i = 0; i < roman.length; i++) {
const current = values[roman[i]]
const next = values[roman[i + 1]]
if (next && current < next) {
result += next - current
i++
} else {
result += current
}
}
return result
}
分層驗證:字符檢查、重複檢查、減法檢查,確保函數健壯性。
// 更新 tests/day15/from-roman.test.ts
import { fromRoman, toRoman } from '../../src/roman/converter'
test('isInverseOfToRoman', () => {
const testNumbers = [1, 4, 5, 9, 10, 40, 90, 400, 900, 1994]
for (const num of testNumbers) {
expect(fromRoman(toRoman(num))).toBe(num)
}
})
執行所有測試來確認功能正常:
npm test tests/day15/
今天我們完成了羅馬數字的反向轉換功能:
明天我們將繼續完善羅馬數字轉換器,加入更多的測試案例和功能增強,確保我們的實作能夠處理各種邊界情況和實際應用需求。
透過 TDD 的紅綠重構循環,我們成功建立了一個穩健的雙向轉換系統,這種方法確保了程式碼的正確性和可維護性 🚀