Create a gist now

Instantly share code, notes, and snippets.

@monkstone /roman_numerals.rb Secret
Last active Jun 4, 2016

What would you like to do?
roman numerals after Sandi Metz
# frozen_string_literal: false
# module that refines String and Numeric
module Roman
refine Fixnum do
def to_roman
RomanNumerals.new.to_s(self)
end
end
refine String do
def to_subtractive_roman
RomanNumeralConversion.new(self).to_subtractive
end
def to_additive_roman
RomanNumeralConversion.new(self).to_additive
end
alias_method :to_i_orig, :to_i
def to_i(type = nil)
type == :roman ? RomanNumerals.new.to_i(self) : to_i_orig
end
end
end
using Roman
# to Roman numeral class
class RomanNumerals
KEY = [1_000, 500, 100, 50, 10, 5, 1]
ROMAN = %w(M D C L X V I)
ROMAN_NUMERALS = KEY.zip(ROMAN).to_h
def to_s(num)
to_roman(num)
end
def to_i(str)
to_number(str)
end
def to_roman(number)
result = ''
ROMAN_NUMERALS.keys.reduce(number) do |to_be_converted, base_10_value|
num_chars_needed, remainder = to_be_converted.divmod(base_10_value)
result << ROMAN_NUMERALS[base_10_value] * num_chars_needed
remainder
end
result.to_subtractive_roman
end
def to_number(roman)
roman.to_additive_roman.chars.reduce(0) do |total, roman_letter|
total + ROMAN_NUMERALS.invert[roman_letter]
end
end
end
# from Roman numeral conversion class
class RomanNumeralConversion
LONG = %w(DCCCC CCCC LXXXX XXXX VIIII IIII)
SHORT = %w(CM CD XC XL IX IV)
LONG_TO_SHORT_MAP = LONG.zip(SHORT).to_h
attr_reader :roman
def initialize(roman)
@roman = roman
end
def to_additive
convert(LONG_TO_SHORT_MAP.invert)
end
def to_subtractive
convert(LONG_TO_SHORT_MAP)
end
def convert(map)
map.keys.reduce(roman) do |converted_roman, alternate_form|
converted_roman.gsub(/#{alternate_form}/, map[alternate_form])
end
end
end
# For the Sandi Metz original sign up to her 99 bottle mailing list
# http://sandimetz.us3.list-manage.com/track/click?u=1090565ccff48ac602d0a84b4&id=bd938590a4&e=8144e160c6
# frozen_string_literal: false
#!/usr/bin/env ruby
gem 'minitest', '>= 5.0.0'
require 'minitest/autorun'
require 'minitest/pride'
require_relative 'roman_numerals'
using Roman
# the test
class RomanNumeralsTest < Minitest::Test
attr_reader :integers
def setup
integer = [1, 4, 7, 11, 19, 49, 54, 99, 899, 999]
roman = %w(I IV VII XI XIX XLIX LIV XCIX DCCCXCIX CMXCIX)
@integers = integer.zip(roman)
end
def test_to_roman
integers.each do |pair|
assert_equal pair[1], pair[0].to_roman
end
end
def test_to_arabic
integers.each do |pair|
assert_equal pair[0], pair[1].to_i(:roman)
end
end
end
# the long to short test
class RomanNumeralConversionTest < Minitest::Test
def test_2
assert_equal 'II', RomanNumeralConversion.new('II').to_subtractive
assert_equal 'II', RomanNumeralConversion.new('II').to_additive
end
def test_4
assert_equal 'IV', RomanNumeralConversion.new('IIII').to_subtractive
assert_equal 'IIII', RomanNumeralConversion.new('IV').to_additive
end
def test_9
assert_equal 'IX', RomanNumeralConversion.new('VIIII').to_subtractive
assert_equal 'VIIII', RomanNumeralConversion.new('IX').to_additive
end
def test_49
assert_equal 'XLIX', RomanNumeralConversion.new('XXXXVIIII').to_subtractive
assert_equal 'XXXXVIIII', RomanNumeralConversion.new('XLIX').to_additive
end
def test_99
assert_equal 'XCIX', RomanNumeralConversion.new('LXXXXVIIII').to_subtractive
assert_equal 'LXXXXVIIII', RomanNumeralConversion.new('XCIX').to_additive
end
def test_449
assert_equal 'CDXLIX', RomanNumeralConversion.new('CCCCXXXXVIIII').to_subtractive
assert_equal 'CCCCXXXXVIIII', RomanNumeralConversion.new('CDXLIX').to_additive
end
def test_999
assert_equal 'XCIX', RomanNumeralConversion.new('LXXXXVIIII').to_subtractive
assert_equal 'LXXXXVIIII', RomanNumeralConversion.new('XCIX').to_additive
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment