问题
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