はじめに
みなさんも一度は 「ローマ数字を相互に変換する(4 ↔ Ⅳ)プログラム書きたいなー!」 と思ったことがありますよね。
実際は以下のような結果を得られる仕様です。
import { romanize, deromanize } from './main.ts'
romanize(1)
// 'I'
romanize(2)
// 'II'
romanize(3)
// 'III'
romanize(4)
// 'Ⅳ'
romanize(2024)
// 'MMXXIV'
deromanize('I')
// 1
deromanize('II')
// 2
deromanize('III')
// 3
deromanize('IV')
// 4
deromanize('MMXXIV')
// 2024
さっそく作っていきましょうー
denoでサクッと準備
まずはdenoのinstall。
brew install deno
プロジェクトの作成
deno init romanize
cd romanize
ファイル内容を確認。treeコマンド便利です。
tree
.
├── deno.json
├── main.ts
└── main_test.ts
テストを先に書ける
main_test.ts
が用意されているので、テストを先に書きはじめられます。
import { assertEquals } from '@std/assert'
// 後で実装するメソッド
import { romanize, deromanize } from './main.ts'
Deno.test('romanize 1 to 10', () => {
assertEquals(romanize(1), 'I')
assertEquals(romanize(2), 'II')
assertEquals(romanize(3), 'III')
assertEquals(romanize(4), 'IV')
assertEquals(romanize(5), 'V')
assertEquals(romanize(6), 'VI')
assertEquals(romanize(7), 'VII')
assertEquals(romanize(8), 'VIII')
assertEquals(romanize(9), 'IX')
assertEquals(romanize(10), 'X')
})
Deno.test('romanize 10 to 100', () => {
assertEquals(romanize(10), 'X')
assertEquals(romanize(20), 'XX')
assertEquals(romanize(30), 'XXX')
assertEquals(romanize(40), 'XL')
assertEquals(romanize(50), 'L')
assertEquals(romanize(60), 'LX')
assertEquals(romanize(70), 'LXX')
assertEquals(romanize(80), 'LXXX')
assertEquals(romanize(90), 'XC')
assertEquals(romanize(100), 'C')
})
Deno.test('romanize 100 to 1000', () => {
assertEquals(romanize(100), 'C')
assertEquals(romanize(200), 'CC')
assertEquals(romanize(300), 'CCC')
assertEquals(romanize(400), 'CD')
assertEquals(romanize(500), 'D')
assertEquals(romanize(600), 'DC')
assertEquals(romanize(700), 'DCC')
assertEquals(romanize(800), 'DCCC')
assertEquals(romanize(900), 'CM')
assertEquals(romanize(1000), 'M')
})
Deno.test('romanize 1000 to 3000', () => {
assertEquals(romanize(1000), 'M')
assertEquals(romanize(2000), 'MM')
assertEquals(romanize(3000), 'MMM')
})
Deno.test('romanize 493', () => {
assertEquals(romanize(493), 'CDXCIII')
})
Deno.test('romanize 2024', () => {
assertEquals(romanize(2024), 'MMXXIV')
})
Deno.test('deromanize 1 to 10', () => {
assertEquals(deromanize('I'), 1)
assertEquals(deromanize('II'), 2)
assertEquals(deromanize('III'), 3)
assertEquals(deromanize('IV'), 4)
assertEquals(deromanize('V'), 5)
assertEquals(deromanize('VI'), 6)
assertEquals(deromanize('VII'), 7)
assertEquals(deromanize('VIII'), 8)
assertEquals(deromanize('IX'), 9)
assertEquals(deromanize('X'), 10)
})
Deno.test('deromanize 10 to 100', () => {
assertEquals(deromanize('X'), 10)
assertEquals(deromanize('XX'), 20)
assertEquals(deromanize('XXX'), 30)
assertEquals(deromanize('XL'), 40)
assertEquals(deromanize('L'), 50)
assertEquals(deromanize('LX'), 60)
assertEquals(deromanize('LXX'), 70)
assertEquals(deromanize('LXXX'), 80)
assertEquals(deromanize('XC'), 90)
assertEquals(deromanize('C'), 100)
})
Deno.test('deromanize 100 to 1000', () => {
assertEquals(deromanize('C'), 100)
assertEquals(deromanize('CC'), 200)
assertEquals(deromanize('CCC'), 300)
assertEquals(deromanize('CD'), 400)
assertEquals(deromanize('D'), 500)
assertEquals(deromanize('DC'), 600)
assertEquals(deromanize('DCC'), 700)
assertEquals(deromanize('DCCC'), 800)
assertEquals(deromanize('CM'), 900)
assertEquals(deromanize('M'), 1000)
})
Deno.test('deromanize 1000 to 3000', () => {
assertEquals(deromanize('M'), 1000)
assertEquals(deromanize('MM'), 2000)
assertEquals(deromanize('MMM'), 3000)
})
Deno.test('deromanize 493', () => {
assertEquals(deromanize('CDXCIII'), 493)
})
Deno.test('deromanize 2024', () => {
assertEquals(deromanize('MMXXIV'), 2024)
})
本番コードにメソッド定義
main.ts
にはメソッドを定義しましょう。
export function romanize(n: number): string {
return ''
}
export function deromanize(r: string): number {
return 0
}
TypeScriptなのも開発体験めちゃくちゃ高いですね。
テストを実行 deno test
まずは失敗することを確認します。
romanize 1 to 10 => ./main_test.ts:4:6
romanize 10 to 100 => ./main_test.ts:17:6
romanize 100 to 1000 => ./main_test.ts:30:6
romanize 1000 to 3000 => ./main_test.ts:43:6
romanize 493 => ./main_test.ts:49:6
romanize 2024 => ./main_test.ts:53:6
deromanize 1 to 10 => ./main_test.ts:56:6
deromanize 10 to 100 => ./main_test.ts:69:6
deromanize 100 to 1000 => ./main_test.ts:82:6
deromanize 1000 to 3000 => ./main_test.ts:95:6
deromanize 493 => ./main_test.ts:101:6
deromanize 2024 => ./main_test.ts:105:6
FAILED | 0 passed | 12 failed (8ms)
error: Test failed
あとは実装コードを書くだけ!
手数少なくテスト書き始められるのが便利でした!
実装コードはチャレンジしてみてください!w
Rubyバージョンのイベント紹介
言語はちがうのですが、この問題のRuby版で、Qiitaでおなじみの凄腕プログラマのコードレビューが受けられるイベントがもうすぐ開催されます!
(本当は、このイベントにJSでも挑戦できるようにしたかったのですが色々調整が間に合わなかったので記事にしておくことにしました。)
JSでの回答例
ネタバレを含みますので、実際に解いてみてからどうぞ。(ツッコミも歓迎!)
const K1 = ['', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX']
const K2 = ['', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC']
const K3 = ['', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM']
const K4 = ['', 'M', 'MM', 'MMM']
const D1 = K1.filter((s) => s.length > 0).reverse()
const D2 = K2.filter((s) => s.length > 0).reverse()
const D3 = K3.filter((s) => s.length > 0).reverse()
const D4 = K4.filter((s) => s.length > 0).reverse()
export function romanize(n: number): string {
const [n4, n3, n2, n1] = String(n)
.padStart(4, '0')
.split('')
.map((x) => parseInt(x))
return K4[n4 % 10] + K3[n3 % 10] + K2[n2 % 10] + K1[n1 % 10]
}
export function deromanize(r: string): number {
let o = r
let sum = 0
D4.forEach((s, i) => {
if (o.startsWith(s)) {
o = o.replace(s, '')
sum += (D4.length - i) * 1000
}
})
D3.forEach((s, i) => {
if (o.startsWith(s)) {
o = o.replace(s, '')
sum += (D3.length - i) * 100
}
})
D2.forEach((s, i) => {
if (o.startsWith(s)) {
o = o.replace(s, '')
sum += (D2.length - i) * 10
}
})
D1.forEach((s, i) => {
if (o.startsWith(s)) {
o = o.replace(s, '')
sum += D1.length - i
}
})
return sum
}