DEV Community

loading...

Daily Challenge #209 - Roman Numerals

thepracticaldev profile image dev.to staff ・1 min read

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)

pic
Editor guide
Collapse
citizen428 profile image
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".

Collapse
jgamgam profile image
Joaquim

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

Collapse
davidcoroian profile image
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);
}
Collapse
sabbin profile image
Sabin Pandelovitch • Edited

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

Collapse
davidcoroian profile image
David

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

Collapse
sabbin profile image
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);
Collapse
empereol profile image
Empereol • Edited

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);
}
Collapse
bjorngrunde profile image
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: ""
Collapse
cipharius profile image
Valts Liepiņš

My solution in Haskell:

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.

Collapse
alexdesousa profile image
Alex de Sousa • Edited

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
Collapse
andreasjakof profile image
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

Collapse
jgamgam profile image
Joaquim • Edited

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