Roman numerals in ruby
In a coding attempt I recently tried, I found this as an alternative solution for converting numbers to roman numerals. I really don't understand how this code works. I just figured out what it was doing divmod
, but also, I am very confused.
class Integer
def to_roman
roman_arr = {
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"
}
num = self
roman_arr.reduce("") do |res, (arab, roman)|
whole_part, num = num.divmod(arab)
res << roman * whole_part
end
end
end
source to share
reduce / fold is functional programming equivalent to loops found in imperative languages. Ruby is capable of both.
foo.reduce("") { |a, i| a + i }
equivalent to
a = ""
foo.each {|i| a = a + i}
a
the string num = self
stores the instance (the number the method receives to_roman
) in a local variable, so you can use it in the block you pass to shrink.
source to share
I added some explanation to the code, hope this is clear now. You will never write it like this, although the code you posted is fine.
class Integer
def to_roman_explained
roman_arr = {
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"
}
remaining = self # the integer on which this method is called
empty_string = "" # startvalue of result
return_value = roman_arr.inject(empty_string) do |result, (arab, roman)|
# inject = reduce, for each element of our hash
# arab and roman are the key and value part of the hash elements, result is result from previous iteration
p [result, arab, roman,remaining.divmod(arab)] # lets see what happens
# number of times the remaining can be divided with the value of this roman, the remaining becomes the rest
whole_part, remaining = remaining.divmod(arab)
result << roman * whole_part # if whole_part == 0 nothing happens for this roman
end
return return_value
end
end
puts 555.to_roman_explained
# gives
# ["", 1000, "M", [0, 555]]
# ["", 900, "CM", [0, 555]]
# ["", 500, "D", [1, 55]] first time the integer is dividable by the value of the roman
# ["D", 400, "CD", [0, 55]] our result now has the roman D
# ["D", 100, "C", [0, 55]]
# ["D", 90, "XC", [0, 55]]
# ["D", 50, "L", [1, 5]] etc
# ["DL", 40, "XL", [0, 5]]
# ["DL", 10, "X", [0, 5]]
# ["DL", 9, "IX", [0, 5]]
# ["DL", 5, "V", [1, 0]] etc
# ["DLV", 4, "IV", [0, 0]]
# ["DLV", 1, "I", [0, 0]]
# DLV
source to share
I found it easier to reverse the number beforehand, like this:
def new_roman(arabic_number)
symbols = {0=>["I","V"],1=>["X","L"],2=>["C","D"],3=>["M"]}
reversed_digits = arabic_number.to_s.split(//).reverse
romans =[]
reversed_digits.length.times do |i|
if reversed_digits[i].to_i< 4
romans<<(symbols[i][0]*reversed_digits[i].to_i)
elsif reversed_digits[i].to_i == 4
romans<<(symbols[i][0]+ symbols[i][1])
elsif reversed_digits[i].to_i == 9
romans<<(symbols[i][0] + symbols[i+1][0])
else
romans<<(symbols[i][1] + (symbols[i][0]*((reversed_digits[i].to_i)-5)))
end
end
romans.reverse.join("")
end
source to share