At the office today we did a coding dojo and the kata that we used was Roman Numerals. We actually did the kata in Java because that was what everyone was familiar with but I decided to work on doing it in Ruby.

After after 20 minutes I got a working solution. However, that's when the fun starts! I spent some time trying several refactorings on the code and didn't really get anywhere. The code that I was refactoring looked roughly like this

@value_map = {
    1 => "I",
    4 => "IV",
    5 => "V",
    9 => "IX",
    10 => "X",
    ...
}

[..., 10, 9, 5, 4, 1].each do |value|
    while(number >= value)
        number -= value
        result += @value_map[value]
    end
end

Because of the expressiveness of Ruby, this code is terse. However, we are covering our edge cases in an odd way. This worked and I couldn't necessarily refactor it to be better. However, I knew that if we added a feature that those edge cases would make it start to look ugly. So, I added a feature (and learned something about roman numerals!). Instead of just going to 3999, we would support 38,999. This meant we needed to add the roman numerals (V) = 5,000 and (X) = 10,000. I added the feature and then refactored and ended up with something like this

class Romans
  def initialize
    @value_map = {
      1 => "I",
      5 => "V",
      10 => "X",
      50 => "L",
      100 => "C",
      500 => "D",
      1000 => "M",
      5000 => "(V)",
      10000 => "(X)",
    }
  end

  def convert integer
    result = ""
    [10_000, 1000, 100, 10, 1].each do |value|
      integer, result = convert_to_highest_significant_digit integer, value, result
    end
    result
  end

  def convert_to_highest_significant_digit integer, value, result  
    if integer >= value * 9
      result += @value_map[value] + @value_map[value * 10]
      integer -= value * 9
    elsif integer >= value * 5
      result += @value_map[value * 5]
      integer -= value * 5
    elsif integer >= value * 4
      result += @value_map[value] + @value_map[value * 5]
      integer -= value * 4
    end

    while integer >= value
      result += @value_map[value]
      integer -= value
    end

    [integer, result]
  end
end

The code above is not without problems, either. We have to lean on the Ruby feature of multiple returns. We also have that ugly if/elsif/elsif statement. We may be able to handle this with another object, though. Here is a more object oriented way

class Romans
  def initialize
    @values = [
      RomanDigitGroup.new(
        RomanDigit.new(1, "I"),
        RomanDigit.new(5, "V"),
        RomanDigit.new(10, "X")
      ),
      RomanDigitGroup.new(
        RomanDigit.new(10, "X"),
        RomanDigit.new(50, "L"),
        RomanDigit.new(100, "C")
      ),
      RomanDigitGroup.new(
        RomanDigit.new(100, "C"),
        RomanDigit.new(500, "D"),
        RomanDigit.new(1000, "M")
      ),
      RomanDigitGroup.new(
        RomanDigit.new(1000, "M"),
        RomanDigit.new(5000, "(V)"),
        RomanDigit.new(10_000, "(X)")
      ),
    ]
  end

  def convert integer
    result = ""
    @values.reverse.each do |value|
      roman = value.convert_to_roman(integer)
      integer -= value.max_represented_value(integer)
      result += roman
    end
    result
  end
end

class RomanDigit
  attr_reader :value
  attr_reader :roman

  def initialize value, roman
    @value = value
    @roman = roman
  end
end

class RomanDigitGroup
  attr_reader :one
  attr_reader :five
  attr_reader :ten

  def initialize one, five, ten
    @one = one
    @five = five
    @ten = ten
    @base_value = one.value
  end

  def convert_to_roman integer
    result = ""

    while integer >= ten_x
      result += @ten.roman
      integer -= ten_x
    end

    if integer >= nine_x
      result += @one.roman + @ten.roman
      integer -= nine_x
    elsif integer >= five_x
      result += @five.roman
      integer -= five_x
    elsif integer >= four_x
      result += @one.roman + @five.roman
      integer -= four_x
    end

    while integer >= one_x
      result += @one.roman
      integer -= one_x
    end

    result
  end

  def max_represented_value integer
    integer_start = integer

    integer -= ten_x while integer >= ten_x

    if integer >= nine_x
      integer -= nine_x
    elsif integer >= five_x
      integer -= five_x
    elsif integer >= four_x
      integer -= four_x
    end

    integer -= one_x while integer >= one_x

    integer_start - integer
  end

  private

  def ten_x
    @base_value * 10
  end

  def nine_x
    @base_value * 9
  end

  def five_x
    @base_value * 5
  end

  def four_x
    @base_value * 4
  end

  def one_x
    @base_value
  end
end 

Unfortunately, that doesn't look exactly right either. We still have the complexity of the while and if/elsifs. The object oriented form may be easier to read and understand but I think it is a little heavy for our purposes (the RomanDigitGroup class conveys how Roman Numerals actually work). This method also allows us to get rid of the hash data structure and the duplication of data (hash and array in the prior implementations).

So, where do I go from here? I'm not really sure. Is there a clever trick to get rid of the ugly while and if statements? Is there a better abstraction that could be applied? Maybe this algorithm has irreducible complexity? I personally like the second version (non object oriented with ugly if statements) best.