Skip to content

Instantly share code, notes, and snippets.

@nwjlyons
Last active November 8, 2025 22:30
Show Gist options
  • Select an option

  • Save nwjlyons/e6c2e9ff6835f582f448e9059813b93c to your computer and use it in GitHub Desktop.

Select an option

Save nwjlyons/e6c2e9ff6835f582f448e9059813b93c to your computer and use it in GitHub Desktop.
Converts a number to a roman numeral.
defmodule RomanNumeral do
@pairs [
{"M", 1_000},
{"CM", 900},
{"D", 500},
{"CD", 400},
{"C", 100},
{"XC", 90},
{"L", 50},
{"XL", 40},
{"X", 10},
{"IX", 9},
{"V", 5},
{"IV", 4},
{"I", 1},
]
@min_number 1
@max_number 3999
@type to_roman_error() :: :less_than_min_number | :greater_than_max_number
@spec to_roman(pos_integer()) :: {:ok, String.t()} | {:error, to_roman_error()}
@doc """
Converts a number to a roman numeral.
## Examples
iex> RomanNumeral.to_roman(1987)
{:ok, "MCMLXXXVII"}
iex> RomanNumeral.to_roman(0)
{:error, :less_than_min_number}
iex> RomanNumeral.to_roman(4000)
{:error, :greater_than_max_number}
"""
def to_roman(number) when number < @min_number, do: {:error, :less_than_min_number}
def to_roman(number) when number > @max_number, do: {:error, :greater_than_max_number}
def to_roman(number) when is_integer(number) do
{[], number}
|> digit("M", 1000)
|> digit("CM", 900)
|> digit("D", 500)
|> digit("CD", 400)
|> digit("C", 100)
|> digit("XC", 90)
|> digit("L", 50)
|> digit("XL", 40)
|> digit("X", 10)
|> digit("IX", 9)
|> digit("V", 5)
|> digit("IV", 4)
|> digit("I", 1)
|> result()
end
defp digit({acc, number}, roman, value) do
q = div(number, value)
r = rem(number, value)
{acc ++ List.duplicate(roman, q), r}
end
defp result({acc, _}), do: {:ok, Enum.join(acc)}
@doc """
Convert a roman numeral to an integer
## Examples
iex> RomanNumeral.parse("IX")
{:ok, 9}
iex> RomanNumeral.parse("LVIII")
{:ok, 58}
iex> RomanNumeral.parse("MCMXCIV")
{:ok, 1994}
iex> RomanNumeral.parse("MCMLXXXVII")
{:ok, 1987}
iex> RomanNumeral.parse("PMCMLXXXVII")
:error
"""
def parse(binary) when is_binary(binary), do: parse(binary, 0)
for {numeral, value} <- @pairs do
defp parse(unquote(numeral) <> rest, acc), do: parse(rest, acc + unquote(value))
end
defp parse("", acc), do: {:ok, acc}
defp parse(_unknown, _acc), do: :error
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment