Roman to integer refactored

China☆狼群 提交于 2021-01-28 02:02:14

问题


I'm writing a method roman_to_integer(roman_string), which translates a roman numeral into its integer: 'IV' to 4, 'XVI' to 16, etc.

 ROMAN_TO_INT = {
      "I" => 1,
      "IV" => 4,
      "V" => 5,
      "IX" => 9,
      "X" => 10,
      "XL" => 40,
      "L" => 50,
      "XC" => 90,
      "C" => 100,
      "CD" => 400,
      "D" => 500,
      "CM" => 900,
      "M" => 1000
    }

    def roman_to_integer(roman_string)
      # TODO: translate roman string to integer
      number = 0
      str = roman_string.dup
      until str.size.zero?
        last_two_characters = str.slice(-2, 2)
        if ROMAN_TO_INT.key?(last_two_characters)
          number += ROMAN_TO_INT[last_two_characters]
          str.chop!
        else
          number += ROMAN_TO_INT[str.slice(-1)]
        end
        str.chop!
      end
      number
    end

How can I make my method shorter? Rubocop only allows 10 lines. I'm trying, but always ended with at least 13.


回答1:


An even smaller version, using the same trick as iGian:

ROMAN_TO_INT =
{
  i: 1,
  v: 5,
  x: 10,
  l: 50,
  c: 100,
  d: 500,
  m: 1000
}

def roman_to_int(roman)
  numbers = roman.downcase.chars.map { |char| ROMAN_TO_INT[char.to_sym] }.reverse
  numbers.inject([0, 1]) do |result_number, int|
    result, number = result_number
    int >= number ? [result + int, int] : [result - int, number]
  end.first
end



回答2:


Not really a refactor, but an option to reduce lines:

ROMAN_TO_INT =
  {
    i: 1,
    v: 5,
    x: 10,
    l: 50,
    c: 100,
    d: 500,
    m: 1000
  }


def roman_to_int roman
  value_map = roman.split('').map { |e| ROMAN_TO_INT[e.downcase.to_sym] }
  value_map.map.with_index do |e, idx| 
    unless value_map[idx + 1].nil?
    then
      value_map[idx + 1] > e ? -e : e
    else e
    end
  end.sum
end

roman = "MDCCLXXVI"
roman_to_int roman #=> 1776

It does not alert in case of invalid roman notation, for example:

roman = "VMII" # incorrect notation for 997
roman_to_int roman #=> 997

roman = "CMXCVII" # correct notation for 997
roman_to_int roman #=> 997



回答3:


If the main objective is to reduce the number of lines of code, one could do the following.

Code

H = {"VI"=>" 4", "XI"=>" 9", "LX"=>" 40", "CX"=>" 90", "DC"=>" 400", "MC"=>" 900",
     "I"=>" 1", "V"=>" 5", "X"=>" 10", "L"=>" 50", "C"=>" 100", "D"=>" 500", "M"=>" 1000"}

def roman_to_integer(roman_string)
   roman_string.reverse.gsub(Regexp.union(H.keys), H).split.sum(&:to_i)
end

Examples

%w| III LXIV CCXXVI CM CMXCVIII MDCCXII |.each {|s| puts "#{s}->#{ roman_to_integer(s)}"}
  # III->3
  # LXIV->64
  # CCXXVI->226
  # CM->900
  # CMXCVIII->998
  # MDCCXII->1712

Explanation

Regular expressions are parsed left-to-right, so to use one here we need to reverse roman_string as a first step. That means we also have to reverse the keys in the hash.

This uses the form of String#gsub that employs a hash as its argument. Notice that the keys of H are in decreasing of order of size. Here's an example of why I've done that. Suppose gsub's pointer is at "V" and the following character is "I". The ordering of the keys will cause gsub (which is greedy) to match "VI" rather than "V".

For

roman_string = "CCXXVI"

the steps are as follows.

k = H.keys
  #=> ["VI", "XI", "LX", "CX", "DC", "MC", "I", "V", "X", "L", "C", "D", "M"]
r = Regexp.union(H.keys)
  #=> /VI|XI|LX|CX|DC|MC|I|V|X|L|C|D|M/
t = s.gsub(r, H)
  #=> " 1 5 10 10 100 100"
a = t.split
  #=> ["1", "5", "10", "10", "100", "100"]
a.sum(&:to_i)
  # => 226

Note that, if we are given

ROMAN_TO_INT = { "I" => 1, "IV" => 4, "V" => 5, "IX" => 9, "X" => 10, "XL" => 40,
                 "L" => 50, "XC" => 90, "C" => 100, "CD" => 400, "D" => 500, 
                 "CM" => 900, "M" => 1000 }

we can calculate H as follows.

H = ROMAN_TO_INT.map { |k,v| [k.reverse, " #{v}"] }.sort_by { |k,_| -k.size }.to_h


来源:https://stackoverflow.com/questions/53033844/roman-to-integer-refactored

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!