# Roman Numerals Kata

In the Ruby world TDD is often demonstrated with a kata of converting decimal numbers into roman numerals. Here’s my take on it with a gentle slope in introducing complexity, with a stepwise discovery of the concepts of…

• repetition
• transliteration
• concatenation
• prefixing

``````class Integer
def to_roman
"I"
end
end
``````

For 2 we discover our first concept: repetition.

``````class Integer
def to_roman
"I" * self
end
end
``````

That works for 3 as well, then we add a special case for 4. We decide, we can live with that.

``````class Integer
def to_roman
return "IV" if self == 4
"I" * self
end
end
``````

This will break again, at 5. To go green asap, we quickly add yet another special case.

``````class Integer
def to_roman
return "V" if self == 5
return "IV" if self == 4
"I" * self
end
end
``````

And then we refactor, thereby discovering the second core concept: transliteration.

``````class Integer
NUMERALS = [[5, "V"], [4, "IV"], [1, "I"]]

def to_roman
NUMERALS.each do |decimal, roman|
count = self / decimal
return roman * count if count > 0
end
end
end
``````

For 6, again, we simply add a special case and move on.

``````class Integer
NUMERALS = [[6, "VI"], [5, "V"], [4, "IV"], [1, "I"]]

def to_roman
NUMERALS.each do |decimal, roman|
count = self / decimal
return roman * count if count > 0
end
end
end
``````

At 7, breaking again, we must get green quickly, even if it comes at the price of a second special case.

``````class Integer
NUMERALS = [[7, "VII"], [6, "VI"], [5, "V"], [4, "IV"], [1, "I"]]
def to_roman
NUMERALS.each do |decimal, roman|
count = self / decimal
return roman * count if count > 0
end
end
end
``````

And because this is now ripe to refactor, we introduce the third concept: concatenation.

``````class Integer
NUMERALS = [[5, "V"], [4, "IV"], [1, "I"]]
def to_roman
result, remainder = "", self
NUMERALS.each do |decimal, roman|
count, remainder = remainder.divmod decimal
result << roman * count
end
result
end
end
``````

Working with the translation table we can complete the converter, also adding error handling.

``````class Integer
NUMERALS = [
[1000, "M"], [900, "CM"], [500, "D"], [400, "CD"],
[ 100, "C"], [ 90, "XC"], [ 50, "L"], [ 40, "XL"],
[  10, "X"], [  9, "IX"], [  5, "V"], [  4, "IV"],
[   1, "I"]
]

def to_roman
raise "There's no such a roman number" if self < 1 || self > 3999
result, remainder = "", self
NUMERALS.each do |decimal, roman|
count, remainder = remainder.divmod decimal
result << roman * count
end
result
end
end
``````

The TDD approach guided us separating the discovery of the core concepts – repetition, transliteration and concatenation – into distinct steps. Nice.

However.

There’s still quite some fugly duplication going on in the translation table, indicating that we’ve ignored the fourth concept lurking behind the remaining double-letter values: prefixing. Whether the additional algorithmic complexity is worth the elimination of conceptual duplication is yet to be seen…