Skip to content

Instantly share code, notes, and snippets.

@savish
Last active December 30, 2017 06:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save savish/5ebcf7990c29b9d71e2ed742a94cdefc to your computer and use it in GitHub Desktop.
Save savish/5ebcf7990c29b9d71e2ed742a94cdefc to your computer and use it in GitHub Desktop.
Generate decimal numbers from roman numbers (basic, no validation)
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