HiveBrain v1.2.0
Get Started
← Back to all entries
snippetrubyMinor

Convert to Roman numerals in Ruby

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
convertromannumeralsruby

Problem

Challenged by Sandy Metz's newsletter, I've tried to implement this kata from exercism. However, my solution, which passes the tests, is quite a lot simpler than Sandy's solution. Ok, so she's using refinements and also supports the inverse, but still... As she's probably smarter than me, do I miss something?

class Fixnum
  ROMAN = {
    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
    result = ""
    ROMAN.reduce(self) { |number, (divider, letter)|
      letter_multiplier, remainder = number.divmod(divider)
      result << (letter * letter_multiplier)
      remainder
    }
    result
  end
end

Solution

I like your solution. You usually see it done with while/for loops that substract multipliers instead of taking the quotient/remainder. Some notes about your code:

  • ROMAN.reduce(self) { |number, (divider, letter)|. In a multiline block is idiomatic to use do/end.



  • Prefer building an array + join at the end, that appending to a string. Not that it matters with those sizes, but in general it's the recommended way (+= in a string is typically O(n^2),



  • inject` is a functional abstraction. While you can perform in-place updates within the block, it will break the assumption of many readers. One solution is to encapsulate the output within the state (using a hash, for example).



I'd write:

class Fixnum
  ROMAN = {...}

  def to_roman
    ROMAN.reduce(number: self, result: []) do |state, (divider, letter)|
      letter_multiplier, remainder = state[:number].divmod(divider)
      new_result = state[:result] + [letter * letter_multiplier]
      {number: remainder, result: new_result}
    end[:result].join
  end
end


Note that this is a state machine: it iterates over an input with an initial state, on each loop the state is updated, and the result is the final state.

Code Snippets

class Fixnum
  ROMAN = {...}

  def to_roman
    ROMAN.reduce(number: self, result: []) do |state, (divider, letter)|
      letter_multiplier, remainder = state[:number].divmod(divider)
      new_result = state[:result] + [letter * letter_multiplier]
      {number: remainder, result: new_result}
    end[:result].join
  end
end

Context

StackExchange Code Review Q#129896, answer score: 3

Revisions (0)

No revisions yet.