【问题标题】:Roman to integer refactored罗马到整数重构
【发布时间】:2019-08-30 01:53:58
【问题描述】:

我正在编写一个方法roman_to_integer(roman_string),它将罗马数字转换为其整数:'IV' 转换为 4,'XVI' 转换为 16,等等。

 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

我怎样才能使我的方法更短? Rubocop 只允许 10 行。我正在尝试,但总是以至少 13 个结尾。

【问题讨论】:

  • 诀窍是缩短您的 ROMAN_CONVERTER 哈希并在您的方法中处理更多案例。请注意,IVIXXC 等都遵循相同的模式:“较小”符号后跟“较大”符号表示您从较大的符号中减去较小的符号。如果您在方法中使用 3 行或更少的行来处理此问题,则可以将散列缩小 6,从而达到 10 行的目标。

标签: ruby refactoring


【解决方案1】:

不是真正的重构,而是减少行数的一种选择:

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

罗马符号无效时不提示,例如:

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

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

【讨论】:

    【解决方案2】:

    一个更小的版本,使用与 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
    

    【讨论】:

      【解决方案3】:

      如果主要目标是减少代码行数,可以执行以下操作。

      代码

      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
      

      示例

      %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
      

      说明

      正则表达式是从左到右解析的,因此要在此处使用正则表达式,我们需要先反转roman_string。这意味着我们还必须反转散列中的键。

      这使用String#gsub 的形式,它采用哈希作为其参数。请注意,H 的键是按大小递减的顺序排列的。这是我为什么这样做的一个例子。假设gsub 的指针在"V",后面的字符是"I"。键的排序将导致gsub(这是贪婪的)匹配"VI"而不是"V"

      对于

      roman_string = "CCXXVI"
      

      步骤如下。

      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
      

      请注意,如果我们给出了

      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 }
      

      我们可以如下计算H

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

      【讨论】:

        猜你喜欢
        • 2023-01-10
        • 1970-01-01
        • 2015-05-16
        • 2022-07-08
        • 1970-01-01
        • 2023-01-17
        • 1970-01-01
        • 2015-07-24
        • 2022-01-13
        相关资源
        最近更新 更多