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 |