Go言語でローマ数字パーサーを作る

# ローマ数字ってなんやねん
https://ja.wikipedia.org/wiki/%E3%83%AD%E3%83%BC%E3%83%9E%E6%95%B0%E5%AD%97

> ローマ数字（ローマすうじ）は、数を表す記号の一種である。ラテン文字の一部を用い、例えばアラビア数字における 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 をそれぞれ I, II, III, IV, V, VI, VII, VIII, IX, X のように表記する。I, V, X, L, C, D, Mはそれぞれ 1, 5, 10, 50, 100, 500, 1000 を表す。i, v, x などと小文字で書くこともある。現代の一般的な表記法では、1以上4000未満の数を表すことができる。

というやつです。時計の文字でたまに見るアイツらです

# きっかけ
ローマ数字をパースしたかった...訳ではないです
アドカレなんか書きたいなーと思っていた時に大学の講義でローマ数字の話が出まして、
パーサー書いたら面白そうだしアドカレのネタにもできそうだなーと思ったのが理由です

:::note
この記事ではアラビア数字->ローマ数字の部分のみを扱います
(逆は作成中です。完成したら記事にするかもしれません)
:::


## インターフェイス
Go言語は静的型付け言語です
静的型付け言語は、同じ計算をする関数を型ごとに用意する必要があるという問題を持っていますが、Go言語ではこれをインターフェイスという仕組みで解決しています。そしてこの仕組みに乗っかることでGo言語っぽいいい感じのコードが書ける...はずです

具体的には、インターフェイスは必要なメソッドのリストとして定義しておき、ある型がそのメソッドすべてを持っていれば自動的にその「インターフェイスを実装した」ことになり、その「インターフェイスを引数に取る関数」にその型を渡すことができるようになります

今回作るパーサーでは、MS Copilot君が教えてくれた`Stringer`インターフェースを使うことにします
`Stringer`インターフェイスはこういうやつです↓
```golang
type Stringer interface {
  String() string
}
```
`String`メソッドがあればいいんですね

## レシーバー
```golang
type Roman uint16

func (r Roman) String() string {
  //☆
}
```
`Roman`型を`uint16`の別名として定義し、`Roman`型に`String`メソッドを定義しています
`String()`の左側の`(r Roman)`は(値)レシーバーと呼ばれ、`rom.String()`として呼んだときの`rom`のデータがコピーされこの`r`に代入されます。
(ポインタレシーバーというのもありますが省略)

## `String`メソッド
☆の部分はこのようになっています
```golang
s := strconv.Itoa(int(r))
ord := len(s)
buf := bytes.NewBuffer(make([]byte, 0, 15))
for i, c := range s {
	buf.WriteString(getDigit(c-'0', ord-i))
}
return buf.String()
```
### for文まで
1. アラビア数字表記の文字列を`s`に代入
    - たとえば`1234`という数値が`"1234"`という文字列になります
2. 桁数を取得
    - 文字のバイト数(=長さ)が桁数と等しいことを利用します
3. 長さ0、容量15のバイト配列から`bytes.Buffer`を生成
    - 変換されたローマ数字を書き込むためのバッファを用意しています

### for文からreturnまで
1. インデックスと文字を取得
    - `range`はスライスからインデックスと対応する要素をループ毎に一つずつ渡してくれます
2. バッファに高い桁から順に書き込んでいく
    - `getDigit`は数値と桁の位をローマ数字に変換する関数です
    - `c-'0'`とすることで`'1'`から`1`に変換しています。(`rune`の引き算)
    -  `ord-i`で何位の桁とするかを計算できます
3. バッファの中身を文字列にして返り値とする
    - 中のバイトスライスの長さ(容量ではない)分が文字列に変換されます
`make([]byte, 0, 15)`でLenとCap両方を指定しているのはこれが理由です

### `getDigit`

中身はこうなっています

 ```golang
var iv_digit =  [...]string{"", "M", "MM", "MMM"}
var iii_digit = [...]string{"", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"}
var ii_digit =  [...]string{"", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"}
var i_digit =   [...]string{"", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"}

func getDigit(c rune, d int) string {
	switch d {
	case 4:
		return iv_digit[c]
	case 3:
		return iii_digit[c]
	case 2:
		return ii_digit[c]
	case 1:
		return i_digit[c]
	default:
		panic("Unexpected error. the number exceeds 10000") //or 4000
	}
}
 ```
 ~~書いていた時はこれが最善だろうと思っていたのですが、
 改めて見ると面白くないですね、これ~~

 桁ごとに0-9と文字を対応つけるために配列を4つ用意します。(千の位は0-3)
 `switch`で位ごとにケースを分けて配列から数値`c`に対応する文字列を返しています

 どれにもあたらないのは想定しません。panicで落ちるようにします

## `NewRoman`関数
フォントの話ではないです。
ここまででローマ数字への変換はできるようになったので最後に`Roman`型を作る関数を作ります
```golang
func NewRoman(n uint16) (Roman, error) {
	if n == 0 {
		return 1, errors.New("0 cannot be presented in roman number")
	}
	if n >= 4000 {
		return 1, errors.New("TOO Large Number. n must be <4000")
	}
	return Roman(n), nil
}
```
といってもこれだけです
ローマ数字は普通に書くと4000未満1以上の整数しか表せないのでこれを表現するようにします
エラーメッセージはよく考えずに書いてます

# 完成
<details open><summary>全体を載せておきます(折りたたみ)</summary>

```golang
type Roman uint16

func NewRoman(n uint16) (Roman, error) {
	if n == 0 {
		return 1, errors.New("0 cannot be presented in roman number")
	}
	if n >= 4000 {
		return 1, errors.New("TOO Large Number. n must be <4000")
	}
	return Roman(n), nil
}

var iv_digit = [...]string{"", "M", "MM", "MMM"}
var iii_digit = [...]string{"", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"}
var ii_digit = [...]string{"", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"}
var i_digit = [...]string{"", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"}

func getDigit(c rune, d int) string {
	switch d {
	case 4:
		return iv_digit[c]
	case 3:
		return iii_digit[c]
	case 2:
		return ii_digit[c]
	case 1:
		return i_digit[c]
	default:
		panic("Unexpected error. the number exceeds 10000") //or 4000
	}
}

func (r Roman) String() string {
	s := strconv.Itoa(int(r))
	ord := len(s)
	buf := bytes.NewBuffer(make([]byte, 0, 15))
	for i, c := range s {
		buf.WriteString(getDigit(c-'0', ord-i))
	}
	return buf.String()
}
```
</details>
これでこういうことができます

```golang
one, _ := NewRoman(1)
two, _ := NewRoman(2)
three, _ := NewRoman(3)
fmt.Print(one, two, three)// --> I II III
fmt.Print(one + two, two * three)// --> III VI
fmt.Print(two * three * three * three)// --> LIV
```

# 課題
書いていて気づいた課題点を挙げたいと思います

- `Roman(n)`で範囲制限を回避できる
- 範囲を超過したまま`String`を呼び出すとpanicで落ちる
- 足し算、掛け算、引き算などでも範囲を超えられる

panicで落ちるのが大きな問題ですね。`String`や`getDigit`でしっかりチェックして表現できない数字が来た時の対処を決めるべきでしょう

`Roman`型は`uint16`の別名として定義したので`uint16`と同じように四則演算ができます。便利ではありますが、負の数や3999を超えると表現できないという制約を持ち込めないのでこれも問題になります

# 終わりに
何かについて記事を書いてネットに公開するというのは今回が初めてでした。
めちゃくちゃ読みづらかったと思いますし、コードも綺麗ではなかったと思いますが、初挑戦ということで許してください!!!