# 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!

Posted on by:

### dev.to staff

The hardworking team behind dev.to ❤️

### Discussion

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".

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

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);
}


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

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

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);
}


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: ""


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.

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


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;
}