# Arabic numbers to Roman numerals conversion

The following converts Arabic numbers to Roman numerals.

I decided to factor each digit into its place value, then use a mapping to convert it to its Roman equivalent.

For example, 1997 is factored to:

$(1 \times 10^3) + (9 \times 10^2) + (9 \times 10^1) + (7 \times 10^0) = 1000 + 900 + 90 + 7$

The Roman numerals for those numbers are then retrieved from the mapping.

class Integer
def to_roman
mappings = {
1 => 'I',
2 => 'II',
3 => 'III',
4 => 'IV',
5 => 'V',
6 => 'VI',
7 => 'VII',
8 => 'VIII',
9 => 'IX',
10 => 'X',
20 => 'XX',
30 => 'XXX',
40 => 'XL',
50 => 'L',
60 => 'LX',
70 => 'LXX',
80 => 'LXXX',
90 => 'XC',
100 => 'C',
200 => 'CC',
300 => 'CCC',
400 => 'CD',
500 => 'D',
600 => 'DC',
700 => 'DCC',
800 => 'DCCC',
900 => 'CM',
1000 => 'M',
2000 => 'MM',
3000 => 'MMM'
}

roman = []
to_s.chars.reverse.each.with_index(0) do |digit, i|
roman.unshift(mappings[digit.to_i * 10**i])
end

roman.join
end
end


I like using exponents to factor each digit into its value place. I don't like having to define a mapping for each number from 1 to 3 and 6 to 8, and repeat that for the 10s, 100s, and 1000s digits.

• Roman numerals sure are very archaic, I hope there is a way to map them more efficiently, but I'm not holding my breath... Apr 15, 2016 at 2:19
• Some nice previous work: codereview.stackexchange.com/questions/7937/… Apr 15, 2016 at 3:05

Similar to dankohn's comment, there are really only a handful of operations going on, just with different character sets.

Here is my quick implementation. It only handles the base 10 digits, and dynamically swaps out character sets depending on the power of the number.

def to_roman(digits)
numeral_sets = {  1=>['I', 'V', 'X'],
10=>['X', 'L', 'C'],
100=>['C', 'D', 'M'],
1000=>['M', "\u2181", "\u2182"] }

str = ''
digits.reverse.each.with_index do |d, ix|
set = numeral_sets[10**ix]
case d
when 0 then roman = ''
when 1..3 then roman = set[0] * d
when 4 then roman = "%s%s" % [set[0], set[1]]
when 5 then roman = set[1]
when 6..8 then roman = "%s%s" % [set[1], set[0] * (d-5)]
when 9 then roman = "%s%s" % [set[0], set[2]]
end
str = str.insert(0, roman)
end
str
end


And a few tests:

puts to_roman [1,9,9,7] # expected MCMXCVII
puts to_roman [1,9,0,7] # expected MCMVII
puts to_roman [5,6,7,8] # expected ↁDCLXXVIII


This should do it too:

class Fixnum
NUMERAL_SETS = {
1000    => "M",
900     => "CM",
500     => "D",
400     => "CD",
100     => "C",
90      => "XC",
50      => "L",
40      => "XL",
10      => "X",
9       => "IX",
5       => "V",
4       => "IV",
1       => "I"
}

def to_roman
result, num = [], self
until num.zero?
NUMERAL_SETS.each do |key, val|
div, mod = num.divmod(key)
result << val * div
num = mod
end
end
result.join ''
end
end


The good thing about this is you don't need to enumerate all the numbers from 1 to 9, then 10 to 90, then 100 to 900, then 1000 to 3000.

The NUMERAL_SETS hash will only contain the bare minimum that you need to construct a Roman numeral.

The divmod method will return [x / y, x % y] so you immediately can append the letter to a result array (complexity: O(n)) and then join this array into a string (complexity: O(n)).

I'm not saying this is the best way to do it, but just thought I might point out another solution :)

You can test with:

RSpec.describe Fixnum do
it '#to_roman converts 1 to I' do
expect(1.to_roman).to eq 'I'
end
it '#to_roman converts 2 to II' do
expect(2.to_roman).to eq 'II'
end
it '#to_roman converts 3 to III' do
expect(3.to_roman).to eq 'III'
end
it '#to_roman converts 5 to V' do
expect(5.to_roman).to eq 'V'
end
it '#to_roman converts 999 to CMXCIX' do
expect(999.to_roman).to eq 'CMXCIX'
end
it '#to_roman converts 532 to DXXXII' do
expect(532.to_roman).to eq 'DXXXII'
end
it '#to_roman converts 2016 to MMXVI' do
expect(2016.to_roman).to eq 'MMXVI'
end
end

• Please provide more critique and/or explain what is better in your solution Jun 2, 2016 at 16:53