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