class Converter NUMERALS = { 50 => "L", 40 => "XL", 10 => "X", 9 => "IX", 5 => "V", 4 => "IV", 1 => "I" } def convert(number) return NUMERALS[number] if NUMERALS[number] to_roman(number) end private def to_roman(number) result = "" NUMERALS.each do |value, numeral| while number >= value result += numeral number -= value end end result end end

Notice the 40, 9, and 4. These aren't unique numerals, rather they are pairs that represent special subtraction rules. Doing this gets my tests to pass and solves the problem, but I wanted to see if I could come up with a way that better represented how roman numerals work.

Looking for inspiration, I asked twitter and google for some help, but everything I found either used this technique or did some magic number substitution (YUCK). So I set out to find my own solution. Notice I am not professing I set out to find a better solution, only my own, alternative solution.

I won't be showing all of the tests as we go along, but the entire solution was test driven with RSpec. You can review the history and see the tests on github.

### Pairing Numerals

After a bit of thought, it occurred to me that I could "pair" numerals. A numeral could be assigned a pair. A numeral and it's pair make a new numeral pair through simple subtraction. For example, five is paired with one. Five's pair_numeral is now 4 with a roman value of "IV".#### The numeral class:

class Numeral attr_accessor :arabic attr_accessor :roman attr_accessor :pair def initialize(*args) if args[0] == args[0].to_i @roman = args[1] @arabic = args[0] else @roman = args[0] @arabic = args[1] end end def pair_numeral return Numeral.new(@arabic - @pair.arabic, @pair.roman + roman) if @pair nil end def ==(compare_to) return @arabic = compare_to.arabic && @roman == compare_to.roman end end

I thought later that I don't really need to pass in parameters in variable order and if I do, I could just use a hash, but I didn't get around to cleaning that one up.

#### The converter class:

require 'numeral' class Converter NUMERALS = { 1000 => Numeral.new(1000,"M"), 500 => Numeral.new(500,"D"), 100 => Numeral.new(100,"C"), 50 => Numeral.new(50,"L"), 10 => Numeral.new(10,"X"), 5 => Numeral.new(5,"V"), 1 => Numeral.new(1,"I") } def initialize @result = "" NUMERALS[5].pair = NUMERALS[1] NUMERALS[10].pair = NUMERALS[1] NUMERALS[50].pair = NUMERALS[10] NUMERALS[100].pair = NUMERALS[10] NUMERALS[500].pair = NUMERALS[100] NUMERALS[1000].pair = NUMERALS[100] end def convert(number) return NUMERALS[number].roman if NUMERALS[number] to_roman(number) @result end private def to_roman(number) NUMERALS.each do |value, numeral| number = roman_reduce(number, numeral) number = roman_reduce(number, numeral.pair_numeral) if numeral.pair_numeral end end def roman_reduce(number, numeral) while number >= numeral.arabic @result += numeral.roman number -= numeral.arabic end number end end

This isn't bad, I suppose. Of course, there's a lot more code than the original solution, but it better represents how roman numerals work. Or does it?

The name pair doesn't really make sense. And while I got rid of the special cases in my NUMERALS hash, I call each of them out in the converter class anyway.

I asked friends on twitter to take a look and give me feedback. It was suggested that I change the names "pair" and "pair_numeral" to something else that better expresses what's really happening. So I changed "pair" to "subtrahend" and "pair_numeral" to "difference".

#### The numeral class:

class Numeral attr_accessor :arabic attr_accessor :roman attr_accessor :subtrahend def initialize(*args) if args[0] == args[0].to_i @roman = args[1] @arabic = args[0] else @roman = args[0] @arabic = args[1] end end def difference return Numeral.new(@arabic - @subtrahend.arabic, @subtrahend.roman + roman) if @subtrahend nil end def ==(compare_to) return @arabic = compare_to.arabic && @roman == compare_to.roman end end

#### The converter class:

require 'numeral' class Converter NUMERALS = { 1000 => Numeral.new(1000,"M"), 500 => Numeral.new(500,"D"), 100 => Numeral.new(100,"C"), 50 => Numeral.new(50,"L"), 10 => Numeral.new(10,"X"), 5 => Numeral.new(5,"V"), 1 => Numeral.new(1,"I") } def initialize @result = "" NUMERALS[5].subtrahend = NUMERALS[1] NUMERALS[10].subtrahend = NUMERALS[1] NUMERALS[50].subtrahend = NUMERALS[10] NUMERALS[100].subtrahend = NUMERALS[10] NUMERALS[500].subtrahend = NUMERALS[100] NUMERALS[1000].subtrahend = NUMERALS[100] end def convert(number) return NUMERALS[number].roman if NUMERALS[number] to_roman(number) @result end private def to_roman(number) NUMERALS.each do |value, numeral| number = roman_reduce(number, numeral) number = roman_reduce(number, numeral.difference) if numeral.difference end end def roman_reduce(number, numeral) while number >= numeral.arabic @result += numeral.roman number -= numeral.arabic end number end end

Now it is obvious that we are subtracting the subtrahend from the minuend in order to ascertain the difference. Right?

Right?

Damn. Still doesn't

*"feel"*right.

I'm trying it again. Stay tuned.

Hey Doc:

ReplyDeleteExcellent and valiant: trying to get the code to represent the problem domain. And, of course, you lost me at subtrahend. ;-)

Actually, in all honesty, what I want is something other than complex-dataset-mapping altogether. I want domain operands (numerals) and operators to represent the underlying arithmetic, as well as the mental operation I do when I look at "IX"

If you could use method chaining sorts of methods, what kinds of operands might emerge on the Numeral class? Including constructors?

Can I have I.X.roman = "IX"

and I.X.arabic = 9 ?

Please?

--Patrick