Skip to content

Instantly share code, notes, and snippets.

@zeitnot
Last active November 19, 2019 08:45
Show Gist options
  • Save zeitnot/a289c6fe3c96f12d3e1f1c47d478b54c to your computer and use it in GitHub Desktop.
Save zeitnot/a289c6fe3c96f12d3e1f1c47d478b54c to your computer and use it in GitHub Desktop.
Ruby Roman numeral converter.
require "test/unit"
InvalidRomanChars = Class.new(StandardError)
OutOfRange = Class.new(StandardError)
InvalidInteger = Class.new(StandardError)
class Roman
I,IV,V,IX,X,XL,L,XC,C,CD,D,CM,M = 1,4,5,9,10,40,50,90,100,400,500,900,1000
LETTERS = %w(IV V IX I XL L XC X CD D CM C M)
def initialize(value)
@value = value
end
def to_roman
@value = @value.to_i
raise InvalidInteger.new('Given value should be integer and greater than zero.') if @value.zero?
raise OutOfRange.new('Given value is greater than 9999') if @value > 9999
roman = ''
until @value.zero?
int = highest_integer
roman << correspondind_roman_letter(int).to_s
@value -= int
end
roman
end
def to_number
@value.strip!
return 0 if @value.size.zero?
@value.upcase!
sum = 0
raise InvalidRomanChars if (@value.chars - LETTERS).any?
until @value.empty?
LETTERS.each do |letter|
if @value.start_with?(letter)
sum += self.class.const_get(letter)
@value[0...letter.size] = ''
break
else
end
end
end
sum
end
private
def highest_integer
digits = @value.digits
digit = digits.last
2.upto(digits.size) do
digit *= 10
end
if correspondind_roman_letter(digit)
digit
else
found = false
digit.downto(1) do |i|
if correspondind_roman_letter(i)
digit = i
found = true
break
end
end
found ? digit : digit.digits.last
end
end
def correspondind_roman_letter(int)
self.class.constants.detect do |cons|
self.class.const_get(cons) == int
end
end
class << self
def to_number(roman_str)
Roman.new(roman_str).to_number
end
def to_roman(int)
Roman.new(int).to_roman
end
end
end
class TestRomanClass < Test::Unit::TestCase
def test_to_roman
assert_equal('I', Roman.to_roman(1))
assert_equal('X', Roman.to_roman(10))
assert_equal('XX', Roman.to_roman(20))
assert_equal('XIX', Roman.to_roman(19))
assert_equal('MCMXC', Roman.to_roman(1990))
assert_equal('MMVIII', Roman.to_roman(2008))
assert_equal('MDCLXVI', Roman.to_roman(1666))
assert_equal('MMMMMMMI', Roman.to_roman(7001))
end
def test_to_roman_with_string_integer
assert_equal('I', Roman.to_roman('1'))
assert_equal('X', Roman.to_roman('10'))
assert_equal('XX', Roman.to_roman('20'))
assert_equal('XIX', Roman.to_roman('19'))
assert_equal('MCMXC', Roman.to_roman('1990'))
assert_equal('MMVIII', Roman.to_roman('2008'))
assert_equal('MDCLXVI', Roman.to_roman('1666'))
assert_equal('MMMMMMMI', Roman.to_roman('7001'))
end
def test_to_roman_with_zero
assert_raise(InvalidInteger) do
Roman.to_roman('0')
end
end
def test_to_roman_with_string_litterals
assert_raise(InvalidInteger) do
Roman.to_roman('test')
end
end
def test_to_roman_out_of_range
assert_raise(OutOfRange) do
Roman.to_roman(10_000_00)
end
end
def test_to_number
assert_equal(0, Roman.to_number(''))
assert_equal(10, Roman.to_number('X'))
assert_equal(20, Roman.to_number('XX'))
assert_equal(19, Roman.to_number('XIX'))
assert_equal(1990, Roman.to_number('MCMXC'))
assert_equal(2008, Roman.to_number('MMVIII'))
assert_equal(1666, Roman.to_number('MDCLXVI'))
end
def test_to_number_when_invalid_roman_chars
assert_raise(InvalidRomanChars) do
Roman.to_number('invalid')
Roman.to_number('1234')
Roman.to_number('xxxa')
end
end
def test_to_numeber_with_lowercase
assert_equal(10, Roman.to_number('x'))
assert_equal(20, Roman.to_number('xx'))
assert_equal(19, Roman.to_number('xix'))
assert_equal(1990, Roman.to_number('mcmxc'))
assert_equal(2008, Roman.to_number('mmviii'))
assert_equal(1666, Roman.to_number('mdclxvi'))
end
end
@zeitnot
Copy link
Author

zeitnot commented Nov 3, 2018

Ruby Roman numeral converter either from Roman letters to integer or from integers to Roman letters.

Usage:

Roman.to_number('XX') # => 20
Roman.to_roman(20) # => XX

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment