想像一下,你正在開發一個歷史教育網站,需要將年份轉換成羅馬數字。昨天的查表策略展現了強大的可擴展性,今天我們只需要加入四個新符號,就能將範圍擴展到 1-3999!🎯
今天我們將專注於:
羅馬數字傳統上止於 3999 的原因:
要處理 1-3999,需要加入:
建立 tests/day14/roman-complete.test.ts
:
import { describe, it, expect } from 'vitest'
import { toRoman } from '../../src/roman/romanNumerals'
describe('Roman Numerals Complete Range', () => {
it('convertsFourHundredToCD', () => {
expect(toRoman(400)).toBe('CD')
})
})
紅燈 🔴!更新 src/roman/romanNumerals.ts
:
export function toRoman(num: number): string {
const values = [
{ value: 1000, symbol: 'M' }, { value: 900, symbol: 'CM' },
{ value: 500, symbol: 'D' }, { value: 400, symbol: 'CD' },
{ value: 100, symbol: 'C' }, { value: 90, symbol: 'XC' },
{ value: 50, symbol: 'L' }, { value: 40, symbol: 'XL' },
{ value: 10, symbol: 'X' }, { value: 9, symbol: 'IX' },
{ value: 5, symbol: 'V' }, { value: 4, symbol: 'IV' },
{ value: 1, symbol: 'I' }
]
let result = ''
for (const { value, symbol } of values) {
while (num >= value) {
result += symbol
num -= value
}
}
return result
}
繼續新增其他符號的測試:
it('convertsFiveHundredToD', () => {
expect(toRoman(500)).toBe('D')
})
it('convertsNineHundredToCM', () => {
expect(toRoman(900)).toBe('CM')
})
it('convertsOneThousandToM', () => {
expect(toRoman(1000)).toBe('M')
})
綠燈 🟢!一次性加入所有符號讓相關測試都通過了。
現在測試一些重要的里程碑數字,驗證我們的實作在複雜組合下的正確性:
it('convertsModernYear', () => {
// 1994 = M(1000) + CM(900) + XC(90) + IV(4)
// 這是一個包含多種減法組合的複雜例子
expect(toRoman(1994)).toBe('MCMXCIV')
})
it('convertsCurrentYear', () => {
// 2024 = MM(2000) + XX(20) + IV(4)
// 測試現代年份的轉換
expect(toRoman(2024)).toBe('MMXXIV')
})
it('convertsMaximumValue', () => {
// 3999 = MMM(3000) + CM(900) + XC(90) + IX(9)
// 這是羅馬數字系統的理論上限
expect(toRoman(3999)).toBe('MMMCMXCIX')
})
it('handlesComplexSubtractionCombinations', () => {
// 測試包含所有減法規則的數字
expect(toRoman(3949)).toBe('MMMCMXLIX') // 3000 + 900 + 40 + 9
expect(toRoman(1444)).toBe('MCDXLIV') // 1000 + 400 + 40 + 4
expect(toRoman(2999)).toBe('MMCMXCIX') // 2000 + 900 + 90 + 9
})
執行測試,綠燈 🟢!我們的查找表方法成功處理了所有複雜組合。
讓我們手動追蹤 1994 的轉換過程:
這個過程展示了貪婪算法在羅馬數字轉換中的完美應用。
羅馬數字使用七個基本符號,展現 1-5-10 的週期性模式:
減法規則:只有 I、X、C 可用於減法,形成 IV(4)、IX(9)、XL(40)、XC(90)、CD(400)、CM(900)。
在這個 Kata 中,我們充分體驗了 TypeScript 與 Vitest 的優勢:
// Vitest 的簡潔寫法
it('convertsModernYear', () => {
expect(toRoman(1994)).toBe('MCMXCIV')
})
// 對比傳統測試框架可能需要更多設置
expect(result).toBe('MCMXCIV')
expect(result).toMatch(/^[MDCLXVI]+$/)
我們的實作達到 O(1) 時間複雜度,因為查找表大小固定(13 個元素)。貪婪算法在羅馬數字中完美適用:選擇最大可用符號總是最佳策略,確保唯一正確表示。
今天我們掌握了:
我們的轉換器特點:
完整實作 src/roman/romanNumerals.ts
:
export function toRoman(num: number): string {
const values = [
{ value: 1000, symbol: 'M' }, { value: 900, symbol: 'CM' },
{ value: 500, symbol: 'D' }, { value: 400, symbol: 'CD' },
{ value: 100, symbol: 'C' }, { value: 90, symbol: 'XC' },
{ value: 50, symbol: 'L' }, { value: 40, symbol: 'XL' },
{ value: 10, symbol: 'X' }, { value: 9, symbol: 'IX' },
{ value: 5, symbol: 'V' }, { value: 4, symbol: 'IV' },
{ value: 1, symbol: 'I' }
]
let result = ''
for (const { value, symbol } of values) {
while (num >= value) {
result += symbol
num -= value
}
}
return result
}
完整測試 tests/day14/roman-complete.test.ts
:
import { describe, it, expect } from 'vitest'
import { toRoman } from '../../src/roman/romanNumerals'
describe('Roman Numerals Complete Range', () => {
it('convertsFourHundredToCD', () => {
expect(toRoman(400)).toBe('CD')
})
it('convertsFiveHundredToD', () => {
expect(toRoman(500)).toBe('D')
})
it('convertsNineHundredToCM', () => {
expect(toRoman(900)).toBe('CM')
})
it('convertsOneThousandToM', () => {
expect(toRoman(1000)).toBe('M')
})
it('convertsModernYear', () => {
expect(toRoman(1994)).toBe('MCMXCIV')
})
it('convertsMaximumValue', () => {
expect(toRoman(3999)).toBe('MMMCMXCIX')
})
})
用 TDD 添加錯誤處理:
describe('Error Handling', () => {
it('throwsErrorForZero', () => {
expect(() => toRoman(0)).toThrow('Number must be positive')
})
it('throwsErrorForNegativeNumbers', () => {
expect(() => toRoman(-1)).toThrow('Number must be positive')
})
it('throwsErrorForNumbersTooLarge', () => {
expect(() => toRoman(4000)).toThrow('Number must be <= 3999')
})
})
實作更新:
export function toRoman(num: number): string {
if (num <= 0) throw new Error('Number must be positive')
if (num > 3999) throw new Error('Number must be <= 3999')
// ... 現有邏輯
}
建立 tests/day14/roman-boundary.test.ts
:
import { describe, it, expect } from 'vitest'
import { toRoman } from '../../src/roman/romanNumerals'
describe('Roman Numeral Boundary Tests', () => {
it('handlesBoundaryValuesCorrectly', () => {
expect(toRoman(1)).toBe('I')
expect(toRoman(3999)).toBe('MMMCMXCIX')
})
it('validatesInputRanges', () => {
expect(() => toRoman(0)).toThrow()
expect(() => toRoman(4000)).toThrow()
})
})
當測試案例增多時,善用 Vitest 的 describe
功能來組織測試。效能優化上,可以重複使用轉換器實例、使用資料提供者進行參數化測試,以及利用 Vitest 的平行測試執行功能。
技術成就:完整羅馬數字系統(1-3999)、Vitest 框架熟練、O(1) 高效實作
開發素養:TDD 紅綠重構、漸進式開發、測試組織、邊界處理
恭喜你完成了 Roman Numeral Kata!這個經典練習為我們的 React TDD 之旅奠定了堅實基礎。明天我們將開始探索新的挑戰!🚀