2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

deno 2.0 「ローマ数字変換プログラム」をTDDで始めるのが便利だった

Posted at

はじめに

みなさんも一度は 「ローマ数字を相互に変換する(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
}
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?