Skip to content

Instantly share code, notes, and snippets.

@riverrun
Last active August 29, 2015 14:25
Show Gist options
  • Save riverrun/7374da9721d4e7f8bb75 to your computer and use it in GitHub Desktop.
Save riverrun/7374da9721d4e7f8bb75 to your computer and use it in GitHub Desktop.
Elixir convert to and from roman numerals
defmodule Roman do
@num_to_roman %{1 => "I", 2 => "II", 3 => "III", 4 => "IV", 5 => "V",
6 => "VI", 7 => "VII", 8 => "VIII", 9 => "IX", 10 => "X", 20 => "XX",
30 => "XXX", 40 => "XL", 50 => "L", 60 => "LX", 70 => "LXX", 80 => "LXXX",
90 => "XC", 100 => "C", 200 => "CC", 300 => "CCC", 400 => "CD", 500 => "D",
600 => "DC", 700 => "DCC", 800 => "DCCC", 900 => "CM", 1000 => "M",
2000 => "MM", 3000 => "MMM"}
@roman_to_num %{"I" => 1, "II" => 2, "III" => 3, "IV" => 4, "V" => 5,
"VI" => 6, "VII" => 7, "VIII" => 8, "IX" => 9, "X" => 10, "XX" => 20,
"XXX" => 30, "XL" => 40, "L" => 50, "LX" => 60, "LXX" => 70, "LXXX" => 80,
"XC" => 90, "C" => 100, "CC" => 200, "CCC" => 300, "CD" => 400, "D" => 500,
"DC" => 600, "DCC" => 700, "DCCC" => 800, "CM" => 900, "M" => 1000,
"MM" => 2000, "MMM" => 3000}
@roman Map.keys @roman_to_num
@invalid ["IIII", "VV", "XXXX", "LL", "CCCC", "DD", "MMMM"]
def translate(input) when is_integer(input) do
find_num(input) |> Enum.map_join(&Map.get(@num_to_roman, &1))
end
def translate(input) do
if :binary.match(input, @invalid) == :nomatch and
Regex.match?(~r/^["I", "V", "X", "L", "C", "D", "M"]*$/, input) do
find_roman(input)
else
raise ArgumentError, message: "Erratum. Numerus invalidus est."
end
end
defp find_num(number) when number > 3999 do
raise ArgumentError, message: "Erratum. Numerus nimis magni est."
end
defp find_num(number) when number > 999 do
[1000 * div(number, 1000), 100 * div(rem(number, 1000), 100),
10 * div(rem(number, 100), 10), rem(number, 10)]
end
defp find_num(number) when number > 99 do
[100 * div(number, 100), 10 * div(rem(number, 100), 10), rem(number, 10)]
end
defp find_num(number) when number > 9 do
[10 * div(number, 10), rem(number, 10)]
end
defp find_num(number) when number > 0, do: [number]
defp find_num(_) do
raise ArgumentError, message: "Erratum. Numerus invalidus est."
end
defp find_roman(input) do
:binary.matches(input, @roman)
|> Enum.map(&Map.get(@roman_to_num, :binary.part(input, &1)))
|> Enum.sum
end
end
Code.load_file("roman.exs")
ExUnit.start
defmodule RomanTest do
use ExUnit.Case, async: true
@roman_low ["I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X"]
@roman_mid ["XXVII", "XLVIII", "LIX", "XCIII", "CXLI", "CLXIII"]
@roman_hi ["CDII", "DLXXV", "CMXI", "MXXIV", "MMM"]
test "1 to 10" do
assert Enum.map(1..10, &Roman.translate(&1)) == @roman_low
end
test "1 to 10 roman to number" do
assert Enum.map(@roman_low, &Roman.translate(&1)) == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
end
test "27, 48, 59, 93, 141, 163" do
assert Enum.map([27, 48, 59, 93, 141, 163], &Roman.translate(&1)) == @roman_mid
end
test "27, 48, 59, 93, 141, 163 roman to number" do
assert Enum.map(@roman_mid, &Roman.translate(&1)) == [27, 48, 59, 93, 141, 163]
end
test "402, 575, 911, 1024, 3000" do
assert Enum.map([402, 575, 911, 1024, 3000], &Roman.translate(&1)) == @roman_hi
end
test "402, 575, 911, 1024, 3000 roman to number" do
assert Enum.map(@roman_hi, &Roman.translate(&1)) == [402, 575, 911, 1024, 3000]
end
test "too big number" do
assert_raise ArgumentError, "Erratum. Numerus nimis magni est.", fn ->
Roman.translate(4500)
end
end
test "invalid number" do
assert_raise ArgumentError, "Erratum. Numerus invalidus est.", fn ->
Roman.translate(-10)
end
end
test "invalid roman numeral" do
assert_raise ArgumentError, "Erratum. Numerus invalidus est.", fn ->
Roman.translate("MHXCI")
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment