Rubyでローマ数字を求める

# ローマ数字(roman numerals)

アラビア数字を入力としてローマ数字を返すメソッドを作成する。


## 回答(リファクタリング前)

<details><summary>無駄が多いし長くて見ない方がいいレベルなので折り畳んでます。</summary><div>

```ruby
def getNums()
  lines = readlines
  len = lines.length

  (0..len-1).each do |i|
    lines[i] = lines[i].chomp.to_i
  end

  return lines
end

class Integer
  def to_roman
    sym = ['I', 'V', 'X', 'L', 'C', 'D', 'M']
    roman = ''
    to_arr(4).each_with_index do |num, i|
      case i
      when 0 then
	roman += sym[6] * num
      when 1 then
	case num
	when 0..3 then
	  roman += sym[4] * num
	when 4 then
	  roman += sym[4] + sym[5]
	when 5..8 then
	  roman += sym[5] + sym[4] * (num-5)
	when 9 then
	  roman += sym[4] + sym[6]
	end
      when 2 then
	case num
	when 0..3 then
	  roman += sym[2] * num
	when 4 then
	  roman += sym[2] + sym[3]
	when 5..8 then
	  roman += sym[3] + sym[2] * (num-5)
	when 9 then
	  roman += sym[2] + sym[4]
	end
      when 3 then
	case num
	when 0..3 then
	  roman += sym[0] * num
	when 4 then
	  roman += sym[0] + sym[1]
	when 5..8 then
	  roman += sym[1] + sym[0] * (num-5)
	when 9 then
	  roman += sym[0] + sym[2]
	end
      end
    end
    roman
  end

  def to_arr(n)
    arr = []
    (Math.log10(self).to_i + 1).upto(n-1) do
      arr.unshift(0)
    end
    arr + digits.reverse
  end
end

if $PROGRAM_NAME == __FILE__
  getNums().each do |i|
    puts i.to_roman
  end
end
```

</div></details>


## 回答(リファクタリング後)

```ruby
def getNums()
  lines = readlines

  (0..lines.length-1).each do |i|
    lines[i] = lines[i].chomp.to_i
  end
  lines
end

class Integer
  def to_roman
    sym = ['I', 'V', 'X', 'L', 'C', 'D', 'M']
    roman = ''
    base = sym.length-1
    to_arr(4).each_with_index do |num, i|
      case num
      when 0..3 then
	roman += sym[base-i*2] * num
      when 4 then
	roman += sym[base-i*2] + sym[base-i*2+1]
      when 5..8 then
	roman += sym[base-i*2+1] + sym[base-i*2] * (num-5)
      when 9 then
	roman += sym[base-i*2] + sym[base-(i-1)*2]
      end
    end
    roman
  end

  def to_arr(n)
    arr = Array.new(n - Math.log10(self).to_i - 1, 0)
    arr + digits.reverse
  end
end

if $PROGRAM_NAME == __FILE__
  getNums().each do |i|
    # puts "#{i} => #{i.to_roman}"
    printf("%4d => %s\n", i, i.to_roman)
  end
end
```


## 解説


### アラビア数字の入力受け取り

```ruby
def getNums()
  lines = readlines

  (0..lines.length-1).each do |i|
    lines[i] = lines[i].chomp.to_i
  end
  lines
end
```

ここは標準入力から入力を受け取る部分です。

-   複数行の入力を対象としているので、改行区切りで入力できます。
-   改行コードを取り除いた文字列から Integer にキャストして、lines に配列として格納していきます。


### 実行部分

```ruby
if $PROGRAM_NAME == __FILE__
  getNums().each do |i|
    # puts "#{i} => #{i.to_roman}"
    printf("%4d => %s\n", i, i.to_roman)
  end
end
```

ここはプログラムを `$ ruby roman_numerals.rb` 等で実行した場合に実行される部分です。

-   先程の `getNums()` で入力を受け取り、その返りオブジェクト(配列) に対して標準出力します。
-   この後の解説で記述しますが、Integerクラスにオーバーライドしているので、 `(Integer).to_roman` で文字列としてローマ数字が取得できます。


### ローマ数字を返すメソッド

```ruby
class Integer
  def to_roman
    sym = ['I', 'V', 'X', 'L', 'C', 'D', 'M']
    roman = ''
    base = sym.length-1
    to_arr(4).each_with_index do |num, i|
      case num
      when 0..3 then
	roman += sym[base-i*2] * num
      when 4 then
	roman += sym[base-i*2] + sym[base-i*2+1]
      when 5..8 then
	roman += sym[base-i*2+1] + sym[base-i*2] * (num-5)
      when 9 then
	roman += sym[base-i*2] + sym[base-(i-1)*2]
      end
    end
    roman
  end

  def to_arr(n)
    arr = Array.new(n - Math.log10(self).to_i - 1, 0)
    arr + digits.reverse
  end
end
```

ここが今回の実装部分になります。

-   Integer クラスにオーバーライドするような実装にしています。


#### 4要素の配列へ(to\_arr(n))

-   ローマ数字は 1〜3999 を表現できます。
    -   `to_arr(n)`
    -   入力されたアラビア数字を各桁で分けて配列化するメソッドです。
    -   `n` は最大桁数を意味し、その桁より少ない数字に対しては 0 埋めされます。
-   例

```ruby
n = 8
n.to_arr(4) # [0, 0, 0, 8]
```


#### ローマ数字へ

-   ローマ数字の各シンボルに関しては [Wikipedia](https://ja.wikipedia.org/wiki/ローマ数字) 等を参照してください。
-   組み立てるローマ数字は文字列の変数 `roman` に格納していきます。
    -   `to_arr(4).each_with_index`
        -   `self` は省略可能なので省略して記述しています。
        -   `to_arr(4)` で 4 要素の配列に変換してから、それぞれの桁に対して処理をおこないます。
    -   `case` 文によって場合分けします。 ローマ数字で重要なのは 4 と 9 です。
        -   それぞれの数字に対しての処理は上記 Wikipedia のローマ数字の概念と配列 `sym = ['I', 'V', 'X', 'L', 'C', 'D', 'M']` を照らし合わせれば理解できます。
    -   最後に出来上がったローマ数字 `roman` を出力します。
-   例

```ruby
n = 8
n.to_roman # "VIII"
n = 1999
n.to_roman # "MCMXCIX"
```


## 実行結果

まず入力として以下のテキストファイル(in.txt)を用意します。

```text
1
2
4
5
6
9
10
11
14
15
19
38
42
49
51
97
99
439
483
499
732
961
999
1999
```

これを入力として与えた時の実行結果が以下。

    $ ruby roman_numerals.rb < in.text
       1 => I
       2 => II
       4 => IV
       5 => V
       6 => VI
       9 => IX
      10 => X
      11 => XI
      14 => XIV
      15 => XV
      19 => XIX
      38 => XXXVIII
      42 => XLII
      49 => XLIX
      51 => LI
      97 => XCVII
      99 => XCIX
     439 => CDXXXIX
     483 => CDLXXXIII
     499 => CDXCIX
     732 => DCCXXXII
     961 => CMLXI
     999 => CMXCIX
    1999 => MCMXCIX

動いた!!!!!



------