Skip to content

Instantly share code, notes, and snippets.

@bapti
Last active April 11, 2022 12:00
Show Gist options
  • Save bapti/f5eb055bad5b09e601f57dcced24e1b5 to your computer and use it in GitHub Desktop.
Save bapti/f5eb055bad5b09e601f57dcced24e1b5 to your computer and use it in GitHub Desktop.
Arabic to Roman Numeral converter in elixir
defmodule RomanNumeralFirstPass do
def convert(value) do
sum =
String.graphemes(value)
|> convert_numerals()
|> Enum.sum()
{:ok, sum}
end
defp convert_numerals(list_of_numerals, numerical_values \\ [])
defp convert_numerals([first | []], numerical_values) do
converted_numeral = convert_numeral(first)
convert_numerals([], [converted_numeral | numerical_values])
end
defp convert_numerals([first, second | tail], numerical_values) do
converted_first = convert_numeral(first)
converted_second = convert_numeral(second)
if converted_first < converted_second do
convert_numerals(tail, [converted_second - converted_first | numerical_values])
else
convert_numerals([second | tail], [converted_first | numerical_values])
end
end
defp convert_numerals([], numerical_values), do: numerical_values
defp convert_numeral(character) do
case character do
"i" -> 1
"v" -> 5
"x" -> 10
"l" -> 50
"c" -> 100
end
end
end
defmodule RomanNumeral do
def convert(roman_numeral) when is_binary(roman_numeral) do
roman_numeral = String.downcase(roman_numeral)
if roman_numeral =~ ~r/[^ivxlc]/i do
raise "Invalid roman numeral #{roman_numeral}"
end
arabic_value = roman_numeral |> String.graphemes() |> sum(0)
{:ok, arabic_value}
end
defp sum([], acc), do: acc
defp sum([x], acc), do: sum([], to_arabic(x) + acc)
defp sum([x, y | tail], acc) do
case {to_arabic(x), to_arabic(y)} do
{a, b} when a < b -> sum(tail, acc + b - a)
{a, _b} -> sum([y | tail], acc + a)
end
end
defp to_arabic("i"), do: 1
defp to_arabic("v"), do: 5
defp to_arabic("x"), do: 10
defp to_arabic("l"), do: 50
defp to_arabic("c"), do: 100
end
defmodule RomanNumeralRefactor do
def convert(value) do
sum =
String.graphemes(value)
|> sum_numerals(0)
{:ok, sum}
end
defp sum_numerals(["i", "v" | tail], sum), do: sum_numerals(tail, sum + 4)
defp sum_numerals(["i", "x" | tail], sum), do: sum_numerals(tail, sum + 9)
defp sum_numerals(["x", "l" | tail], sum), do: sum_numerals(tail, sum + 40)
defp sum_numerals(["x", "c" | tail], sum), do: sum_numerals(tail, sum + 90)
defp sum_numerals(["i" | tail], sum), do: sum_numerals(tail, sum + 1)
defp sum_numerals(["v" | tail], sum), do: sum_numerals(tail, sum + 5)
defp sum_numerals(["x" | tail], sum), do: sum_numerals(tail, sum + 10)
defp sum_numerals(["l" | tail], sum), do: sum_numerals(tail, sum + 50)
defp sum_numerals(["c" | tail], sum), do: sum_numerals(tail, sum + 100)
defp sum_numerals([], sum), do: sum
end
defmodule RomanNumeral do
def convert(roman_numeral) when is_binary(roman_numeral) do
roman_numeral = roman_numeral |> String.downcase()
if roman_numeral =~ ~r/[^ivxlc]/i do
raise "Invalid roman numeral #{roman_numeral}"
end
arabic_value = roman_numeral |> sum(0)
{:ok, arabic_value}
end
defp sum("iv" <> rest, acc), do: sum(rest, acc + 4)
defp sum("ix" <> rest, acc), do: sum(rest, acc + 9)
defp sum("xl" <> rest, acc), do: sum(rest, acc + 40)
defp sum("xc" <> rest, acc), do: sum(rest, acc + 90)
defp sum("i" <> rest, acc), do: sum(rest, acc + 1)
defp sum("v" <> rest, acc), do: sum(rest, acc + 5)
defp sum("x" <> rest, acc), do: sum(rest, acc + 10)
defp sum("l" <> rest, acc), do: sum(rest, acc + 50)
defp sum("c" <> rest, acc), do: sum(rest, acc + 100)
defp sum("", acc), do: acc
end
@bapti
Copy link
Author

bapti commented Apr 9, 2022

This was a code test for a contract position I was applying for in 2022.

My first solution took a little while to get there, I started off TDD-ing and doing a very simplistic approach to the problem. I also hadn't seen the patterns of a roman numeral that if there's a pair of numbers together, like ix or iv the case is that the left is lower than the right. I'd have rather gone the route of special cases like I did with solution 2 and 3 but was directed to write the logic.

I added solution 1a as a refactored version of 1 that maintains the logic of the numerals, but makes better use of pattern matching, I still think version 3 is the neatest solution, I think the 4 special cases (would be 6 if we included M and D) is the easiest to follow in terms of decoding a roman numeral.

Notes to future me

  1. When presented with something like roman numerals, go and read the wikipedia article, for this problem is clearly explains the special cases for 4, 9, 40, 90 etc.
  2. Before going into a face to face code test, make sure to practise a few of these types of problems first. Exercism has some good puzzles, so does something like advent of code. Practising for a few hours, and verbalising coding is good when you've not done it for a while.
  3. Don't be afraid to stick to your guns when designing a solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment