5 # Generates roman numerals from integers and vice-versa
\r
7 # A response to Ruby Quiz of the Week #22 - Roman Numerals [ruby-talk:132925]
\r
9 # Author: Dave Burt <dave at burt.id.au>
\r
11 # Created: 7 Mar 2005
\r
13 # Last modified: 8 Mar 2005
\r
15 # Fine print: Provided as is. Use at your own risk. Unauthorized copying is
\r
16 # not disallowed. Credit's appreciated if you use my code. I'd
\r
17 # appreciate seeing any modifications you make to it.
\r
20 # Contains methods to convert integers to roman numeral strings and vice-versa.
\r
21 module RomanNumerals
\r
23 # Maps roman numeral digits to their integer values
\r
34 # The largest integer representable as a roman numerable by this module
\r
37 # Maps some integers to their roman numeral values
\r
38 @@digits_lookup = DIGITS.inject({
\r
44 900 => 'CM',}) do |memo, pair|
\r
45 memo.update({pair.last => pair.first})
\r
48 # Stolen from O'Reilly's Perl Cookbook 6.23. Regular Expression Grabbag
\r
49 REGEXP = /^M*(D?C{0,3}|C[DM])(L?X{0,3}|X[LC])(V?I{0,3}|I[VX])$/i
\r
51 # Converts +int+ to a roman numeral
\r
52 def self.from_integer(int)
\r
53 return nil if int < 0 || int > MAX
\r
56 @@digits_lookup.keys.sort.reverse.each do |digit_value|
\r
57 while remainder >= digit_value
\r
58 remainder -= digit_value
\r
59 result += @@digits_lookup[digit_value]
\r
61 break if remainder <= 0
\r
66 # Converts +roman_string+, a roman numeral, to an integer
\r
67 def self.to_integer(roman_string)
\r
68 return nil unless roman_string.is_roman_numeral?
\r
70 roman_string.to_s.upcase.split(//).reverse.inject(0) do |memo, digit|
\r
71 if digit_value = DIGITS[digit]
\r
72 if last && last > digit_value
\r
83 # Returns true iif +string+ is a roman numeral.
\r
84 def self.is_roman_numeral?(string)
\r
90 # Considers string a roman numeral numeral,
\r
91 # and converts it to the corresponding integer.
\r
93 RomanNumerals.to_integer(self)
\r
95 # Returns true iif the subject is a roman numeral.
\r
96 def is_roman_numeral?
\r
97 RomanNumerals.is_roman_numeral?(self)
\r
101 # Converts this integer to a roman numeral.
\r
103 RomanNumerals.from_integer(self) || ''
\r
108 # Integers that look like roman numerals
\r
110 attr_reader :to_s, :to_i
\r
112 @@all_roman_numerals = []
\r
114 # May be initialized with either a string or an integer
\r
115 def initialize(value)
\r
118 @to_s = value.to_s_roman
\r
122 @to_i = value.to_s.to_i_roman
\r
124 @@all_roman_numerals[to_i] = self
\r
127 # Factory method: returns an equivalent existing object if such exists,
\r
129 def self.get(value)
\r
130 if value.is_a?(Integer)
\r
133 to_i = value.to_s.to_i_roman
\r
135 @@all_roman_numerals[to_i] || RomanNumeral.new(to_i)
\r
142 # Delegates missing methods to Integer, converting arguments to Integer,
\r
143 # and converting results back to RomanNumeral
\r
144 def method_missing(sym, *args)
\r
145 unless to_i.respond_to?(sym)
\r
146 raise NoMethodError.new(
\r
147 "undefined method '#{sym}' for #{self}:#{self.class}")
\r
149 result = to_i.send(sym,
\r
150 *args.map {|arg| arg.is_a?(RomanNumeral) ? arg.to_i : arg })
\r
153 RomanNumeral.get(result)
\r
155 result.map do |element|
\r
156 element.is_a?(Integer) ? RomanNumeral.get(element) : element
\r
164 # Enables uppercase roman numerals to be used interchangeably with integers.
\r
165 # They are auto-vivified RomanNumeral constants
\r
169 # III ** III #=> XXVII
\r
170 # VIII.divmod(III) #=> [II, II]
\r
171 def Object.const_missing sym
\r
172 raise NameError.new("uninitialized constant: #{sym}") unless RomanNumerals::REGEXP === sym.to_s
\r
173 const_set(sym, RomanNumeral.get(sym))
\r
177 # Quiz solution: filter that swaps roman and arabic numbers
\r
179 ARGF.each do |line|
\r
181 if line.is_roman_numeral?
\r
182 puts line.to_i_roman
\r
184 puts line.to_i.to_s_roman
\r