DEV Community is a community of 879,630 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

dev.to staff

Posted on

Daily Challenge #209 - Roman Numerals

Implement a function that takes a Roman numeral as its argument and returns its value as an integer. You don't need to validate the form of the Roman numeral.

Modern Roman numerals are written by expressing each digit of the number to be encoded separately, starting with the leftmost digit and skipping any 0s. So 1990 is rendered "MCMXC" (1000 = M, 900 = CM, 90 = XC) and 2008 is rendered "MMVIII" (2000 = MM, 8 = VIII). The Roman numeral for 1666, "MDCLXVI", uses each letter in descending order.

Here's a chart to help out:

```Symbol    Value
I          1
V          5
X          10
L          50
C          100
D          500
M          1,000```

Example

solution('XXI'); // should return 21

Tests

solution('I')
solution('IV')
solution('MMVIII')
solution('MDCLXVI')

Good luck!

This challenge comes from jhoffner on CodeWars. Thank you to CodeWars, who has licensed redistribution of this challenge under the 2-Clause BSD License!

Want to propose a challenge idea for a future post? Email yo+challenge@dev.to with your suggestions!

Discussion (12)

Michael Kohl

Not a solution to this challenge, but I always loved the fact that Common Lisp's `format` function actually has a formatting directive for roman numerals:

``````(format nil "~@r" 1666)
==> MDCLXVI
``````

There's even a directive for old-style Roman numerals where 4 is IIII and 9 VIIII etc., `"~:@r"`.

Joaquim

Hah, there were lots of latin freaks out there in the 60s. Nice, didn't know about it.

David
``````const mapRomanNToN = {
'I': 1,
'V': 5,
'X': 10,
'L': 50,
'C': 100,
'D': 500,
'M': 1000,
}

const solution = (str) => {
const reducer = (acc, curVal) => {
return acc += mapRomanNToN[curVal];
}

return str.split('').reduce(reducer, 0);
}
``````
Sabin Pandelovitch • Edited on

Not working, due to the fact that you ignored that if a value is smaller than the next one it's substracted not added... check the test case MCMXC it should be 1990 and yours is 2210

David

haha you re right! thanks. Was in a rush. will fix it

Sabin Pandelovitch

JS solution

``````const v = {
I: 1,
V: 5,
X: 10,
L: 50,
C: 100,
D: 500,
M: 1000
};

const solution = nr => nr.split("").reduce((acc,cur,i,oa)=> {
acc = i<oa.length-1 ? (v[cur] >= v[oa[i+1]] ? acc+v[cur] : acc - v[cur]) : acc+ v[cur];
return acc;
},0);
``````

TypeScript

``````type RomanNumeral = 'I' | 'V' | 'X' | 'L' | 'C' | 'D' | 'M';

const RomanNumerals: Record<RomanNumeral, number> = {
I: 1,
V: 5,
X: 10,
L: 50,
C: 100,
D: 500,
M: 1000
};

/**
* Convert a string of Roman Numerals to an integer
* @param input String of Roman Numerals. Invalid characters are not considered.
*/
function solution(input: string): number {
return Array.from(input)
.map(n => RomanNumerals[n])
.filter(Number)
.reduce((total, val, idx, arr) => {
if (val >= (arr[idx + 1] || 0)) {
return (total += val);
} else {
return (total -= val);
}
}, 0);
}
``````
Björn Grunde

Fun solution in elixir

``````def numerals(num) when num > 1000, do: "M" <> numerals(num - 1000)
def numerals(num) when num > 900, do: "CM" <> numerals(num - 900)
def numerals(num) when num > 500, do: "D" <> numerals(num - 500)
def numerals(num) when num > 400, do: "CD" <> numerals(num - 400)
def numerals(num) when num > 100, do: "C" <> numerals(num - 100)
def numerals(num) when num > 90, do: "XC" <> numerals(num - 90)
def numerals(num) when num > 50, do: "L" <> numerals(num - 50)
def numerals(num) when num > 40, do: "XL" <> numerals(num - 40)
def numerals(num) when num > 10, do: "X", <> numerals(num - 10)
def numerals(num) when num > 9, do: "IX" <> numerals(num - 9)
def numerals(num) when num > 5, do: "V" <> numerals(num - 5)
def numerals(num) when num > 4, do: "IV" <> numerals(num - 4)
def numerals(num) when num > 1, do: "I" <> numerals(num - 1)
def numerals(0), do: ""
``````
Valts Liepiņš

``````solution :: String -> Int
solution = parser . fmap lexer
where
parser :: [Int] -> Int
parser []     = 0
parser (x:[]) = x
parser (x:x':xs)
| x >= x'   = x + (parser (x':xs))
| otherwise = -x + x' + (parser xs)

lexer :: Char -> Int
lexer s =
case s of
'I' -> 1
'V' -> 5
'X' -> 10
'L' -> 50
'C' -> 100
'D' -> 500
'M' -> 1000
``````

The lexer function takes care of translating roman numeral tokens into numbers, which can be applied to whole string to get array of the respective integers.
The array of integers is then parsed with a recursive function.

I went with case in lexer, since that notation seemed neater than defining function for each argument case.

Alex de Sousa • Edited on

Using pattern matching and tail recursion in Elixir:

``````defmodule Roman do
@moduledoc false

@spec to_integer(binary()) :: {:ok, pos_integer()} | :error
def to_integer(number, acc \\ 0)

def to_integer("", acc), do: {:ok, acc}

def to_integer(number, acc) do
with {value, rest} <- num(number) do
to_integer(rest, acc + value)
end
end

@spec num(binary()) :: {pos_integer(), binary()} | :error
defp num(number)

defp num("M" <> rest), do: {1000, rest}
defp num("CM" <> rest), do: {900, rest}
defp num("D" <> rest), do: {500, rest}
defp num("CD" <> rest), do: {400, rest}
defp num("C" <> rest), do: {100, rest}
defp num("XC" <> rest), do: {90, rest}
defp num("L" <> rest), do: {50, rest}
defp num("XL" <> rest), do: {40, rest}
defp num("X" <> rest), do: {10, rest}
defp num("IX" <> rest), do: {9, rest}
defp num("V" <> rest), do: {5, rest}
defp num("IV" <> rest), do: {4, rest}
defp num("I" <> rest), do: {1, rest}
defp num(_), do: :error
end

solution = fn x ->
case Roman.to_integer(x) do
{:ok, value} -> value
:error -> raise ArgumentError, message: "Wrong roman numeral format"
end
end
``````
Andreas Jakof

if you are going from back to front, it is a bit easier to "parse".
So this time F# instead of my usual C# posts.

Yes, still learning...

``````let roman list : int =
let getNumeralValue x =
match x with
| 'M' -> 1000
| 'm' -> 1000
| 'D' -> 500
| 'd' -> 500
| 'C' -> 100
| 'c' -> 100
| 'L' -> 50
| 'l' -> 50
| 'X' -> 10
| 'x' -> 10
| 'V' -> 5
| 'v' -> 5
| 'I' -> 1
| 'i' -> 1
| _ -> 0
let folder (sum,last) current =
if last > current
then (sum-current,last)
else (sum+current,current)
Seq.rev list
|> Seq.map getNumeralValue
|> Seq.fold folder (0,0)
|> fst

``````

I did mine using JavaScript, not really a fast algorithm but I think it's readable and allows your pals to understand what's going on.

``````function romanCharToInt(c) {
switch (c) {
case 'I':
return 1;
case 'V':
return 5;
case 'X':
return 10;
case 'L':
return 50;
case 'C':
return 100;
case 'D':
return 500;
case 'M':
return 1000;
}
}

function romanToInt(s) {
let finalSum = 0;
let romanChars = s.split("");

for (let i = 0; i < romanChars.length; i++) {
if (romanChars[i + 1]) {
if (romanCharToInt(romanChars[i]) >= romanCharToInt(romanChars[i + 1])) {
finalSum += romanCharToInt(romanChars[i]);
} else {
finalSum += romanCharToInt(romanChars[i + 1]);
finalSum -= romanCharToInt(romanChars[i]);
i++;
}
} else {
finalSum += romanCharToInt(romanChars[i]);
}
}

return finalSum;
}
``````