This page contains automated test results for code from O'Reilly's Ruby Cookbook. If this code looks interesting or useful, you might want to buy the whole book.
| Doing Math with Roman Numbers | ||
|---|---|---|
| Code | Expected | Actual |
class Roman
# These arrays map all distinct substrings of Roman numbers
# to their Arabic equivalents, and vice versa.
@@roman_to_arabic = [['M', 1000], ['CM', 900], ['D', 500], ['CD', 400],
['C', 100], ['XC', 90], ['L', 50], ['XL', 40], ['X', 10], ['IX', 9],
['V', 5], ['IV', 4], ['I', 1]]
@@arabic_to_roman = @@roman_to_arabic.collect { |x| x.reverse }.reverse
# The Roman symbol for 5000 (a V with a bar over it) is in neither
# ASCII nor Unicode, so we won't represent numbers larger than 3999.
MAX = 3999
def initialize(number)
if number.respond_to? :to_str
@value = Roman.to_arabic(number)
else
Roman.assert_within_range(number)
@value = number
end
end
# Raise an exception if a number is too large or small to be represented
# as a Roman number.
def Roman.assert_within_range(number)
unless number.between?(1, MAX)
msg = "#{number} can't be represented as a Roman number."
raise RangeError.new(msg)
end
end
#Find the Fixnum value of a string containing a Roman number.
def Roman.to_arabic(s)
value = s
if s.respond_to? :to_str
c = s.dup
value = 0
invalid = ArgumentError.new("Invalid Roman number: #{s}")
value_of_previous_number = MAX+1
value_from_previous_number = 0
@@roman_to_arabic.each_with_index do |(roman, arabic), i|
value_from_this_number = 0
while c.index(roman) == 0
value_from_this_number += arabic
if value_from_this_number >= value_of_previous_number
raise invalid
end
c = c[roman.size..s.size]
end
#This one's a little tricky. We reject numbers like "IVI" and
#"IXV", because they use the subtractive notation and then
#tack on a number that makes the total overshoot the number
#they'd have gotten without using the subtractive
#notation. Those numbers should be V and XIV, respectively.
if i > 2 and @@roman_to_arabic[i-1][0].size > 1 and
value_from_this_number + value_from_previous_number >=
@@roman_to_arabic[i-2][1]
raise invalid
end
value += value_from_this_number
value_from_previous_number = value_from_this_number
value_of_previous_number = arabic
break if c.size == 0
end
raise invalid if c.size > 0
end
return value
end
def to_arabic
@value
end
#Render a Fixnum as a string depiction of a Roman number
def to_roman
value = to_arabic
Roman.assert_within_range(value)
repr = ""
@@arabic_to_roman.reverse_each do |arabic, roman|
num, value = value.divmod(arabic)
puts "Roman #{roman} num #{num}"
repr << roman * num
end
repr
end
# Delegate all methods to the stored integer value. If the result is
# a Integer, transform it into a Roman object. If it's an array
# containing Integers, transform it into an array containing Roman
# objects.
def method_missing(m, *args)
super unless @value.respond_to?(m)
hex_args = args.collect do |arg|
arg.kind_of?(Roman) ? arg.to_int : arg
end
result = @value.send(m, *hex_args)
return result if m == :coerce
begin
case result
when Integer
Roman.new(result)
when Array
result.collect do |element|
element.kind_of?(Integer) ? Roman.new(element) : element
end
else
result
end
rescue RangeError
# Too big or small to fit in a Roman number. Use the Fixnum.
result
end
end
def respond_to?(method_name)
super or @value.respond_to? method_name
end
def to_s
to_roman
end
def inspect
to_s
end
end
class Fixnum
def to_roman
Roman.new(self)
end
end
class String
def to_roman
Roman.new(self)
end
end
72.to_roman |
LXXII | LXXII |
444.to_roman |
CDXLIV | CDXLIV |
1979.to_roman |
MCMLXXIX | MCMLXXIX |
'MCMXLVIII'.to_roman |
MCMXLVIII | MCMXLVIII |
Roman.to_arabic('MCMLXXIX') |
1979 | 1979 |
'MMI'.to_roman.to_arabic |
2001 | 2001 |
'MMI'.to_roman + 3 |
MMIV | MMIV |
'MCMXLVIII'.to_roman |
MCMXLVIII | MCMXLVIII |
612.to_roman * 3.to_roman |
MDCCCXXXVI | MDCCCXXXVI |
(612.to_roman * 3).divmod('VII'.to_roman) |
[CCLXII, II] | [CCLXII, II] |
612.to_roman * 10000 |
6120000 | 6120000 |
612.to_roman * 0 |
0 | 0 |
'MCMXCIX'.to_roman.succ |
MM | MM |
('I'.to_roman..'X'.to_roman).collect |
[I, II, III, IV, V, VI, VII, VIII, IX, X] | [I, II, III, IV, V, VI, VII, VIII, IX, X] |
'IIII'.to_roman |
ArgumentError: Invalid Roman number: IIII ... |
ArgumentError: Invalid Roman number: IIII from (irb):41:in `to_arabic' from (irb):140:in `each_with_index' from (irb):36:in `to_arabic' from (irb):13:in `initialize' from (irb):123:in `to_roman' from (irb):140 from :0 |
'IVI'.to_roman |
ArgumentError: Invalid Roman number: IVI ... |
ArgumentError: Invalid Roman number: IVI from (irb):53:in `to_arabic' from (irb):141:in `each_with_index' from (irb):36:in `to_arabic' from (irb):13:in `initialize' from (irb):123:in `to_roman' from (irb):141 from :0 |
'IXV'.to_roman |
ArgumentError: Invalid Roman number: IXV ... |
ArgumentError: Invalid Roman number: IXV from (irb):53:in `to_arabic' from (irb):142:in `each_with_index' from (irb):36:in `to_arabic' from (irb):13:in `initialize' from (irb):123:in `to_roman' from (irb):142 from :0 |
'MCMM'.to_roman |
ArgumentError: Invalid Roman number: MCMM ... |
ArgumentError: Invalid Roman number: MCMM from (irb):60:in `to_arabic' from (irb):13:in `initialize' from (irb):123:in `to_roman' from (irb):143 from :0 |
'CIVVM'.to_roman |
ArgumentError: Invalid Roman number: CIVVM ... |
ArgumentError: Invalid Roman number: CIVVM from (irb):60:in `to_arabic' from (irb):13:in `initialize' from (irb):123:in `to_roman' from (irb):144 from :0 |
-10.to_roman |
RangeError: -10 can't be represented as a Roman number. ... |
RangeError: -10 can't be represented as a Roman number. from (irb):24:in `assert_within_range' from (irb):15:in `initialize' from (irb):118:in `to_roman' from (irb):145 from :0 |
50000.to_roman |
RangeError: 50000 can't be represented as a Roman number. ... |
RangeError: 50000 can't be represented as a Roman number. from (irb):24:in `assert_within_range' from (irb):15:in `initialize' from (irb):118:in `to_roman' from (irb):146 from :0 |