Last active
December 30, 2017 06:41
-
-
Save savish/5ebcf7990c29b9d71e2ed742a94cdefc to your computer and use it in GitHub Desktop.
Generate decimal numbers from roman numbers (basic, no validation)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Roman do | |
@values [i: 1, v: 5, x: 10, l: 50, c: 100, d: 500, m: 1000] | |
@valid_transitions %{ | |
m: [:c, :d, :l, :x, :v, :i], | |
d: [:c, :l, :x, :v, :i], | |
c: [:m, :d, :c, :l, :x, :v, :i], | |
l: [:x, :v, :i], | |
x: [:c, :l, :x, :v, :i], | |
v: [:i], | |
i: [:x, :v, :i] | |
} | |
@doc """ | |
Converts a roman numeral string into its decimal equivalent. | |
Catches most validation errors, but some still persist. | |
## Example | |
iex> Roman.to_decimal("MCM") | |
{:ok, 1900} | |
iex> Roman.to_decimal("MCMIC") | |
{:error, "Invalid roman numeral"} | |
This works, but it shouldn't | |
iex> Roman.to_decimal("MCMIVII") | |
{:ok, 1906} | |
""" | |
def to_decimal(roman_string) do | |
atom_list = roman_string | |
|> String.graphemes | |
|> Enum.map(&String.downcase(&1) |> String.to_atom) | |
if atom_list |> is_valid? do | |
{:ok, convert(atom_list |> List.pop_at(0))} | |
else | |
{:error, "Invalid roman numeral"} | |
end | |
end | |
def to_decimal!(roman_string) do | |
case to_decimal(roman_string) do | |
{:ok, val} -> val | |
{:error, _} -> raise ArgumentError, message: "Invalid roman numeral" | |
end | |
end | |
defp is_valid?(atom_list) do | |
List.zip([atom_list, tl(atom_list) ++ [nil]]) | |
|> Enum.reduce(true, fn(tp, acc) -> is_tuple_valid?(tp) and acc end) | |
end | |
defp is_tuple_valid?({from, to}) do | |
to in @valid_transitions[from] or to == nil | |
end | |
defp convert(_, acc \\ 0) | |
defp convert({atm, rest}, acc) when atm in [:v, :l, :d, :m] do | |
convert(rest |> List.pop_at(0), acc + @values[atm]) | |
end | |
defp convert({atm, rest}, acc) when atm in [:i, :x, :c] do | |
{base, remainder} = List.pop_at(rest, 0) | |
if @values[base] && @values[base] > @values[atm] do | |
convert(remainder |> List.pop_at(0), acc + @values[base] - @values[atm]) | |
else | |
convert(rest |> List.pop_at(0), acc + @values[atm]) | |
end | |
end | |
defp convert({nil, []}, acc) do | |
acc | |
end | |
end | |
roman_numeral = "MCMIV" | |
# Convert while throwing an error on invalid conversions | |
# | |
# IO.puts "#{roman_numeral} = #{Roman.to_decimal!(roman_numeral)}" | |
case Roman.to_decimal(roman_numeral) do | |
{:ok, val} -> IO.puts "#{roman_numeral} = #{val}" | |
{:error, error} -> IO.puts "#{error} (#{roman_numeral})" | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment