Check if an input is a valid roman numeral

Question:

I got a program that converts Roman numerals to integers and vice versa. My problem is that I don´t really know how to create a function that checks if the user input is a valid roman numeral. My code right now:

def checkIfRomanNumeral(numeral):
"""Controls that the userinput only contains valid roman numerals"""
    numeral = numeral.upper()
    validRomanNumerals = ["M", "D", "C", "L", "X", "V", "I", "(", ")"]
    for letters in numeral:
        if letters not in validRomanNumerals:
            print("Sorry that is not a valid roman numeral")
            return True
        elif letters in validRomanNumerals:
            romanToInt(numeral)
            break

I think the problem right now is that the function only checks the first letter in the input(numeral) because of the for loop. Could someone help me to make the function check the whole input and print(“Sorry that is not a valid Roman numeral”) if any letter of the input is not a Roman numeral. The parenthesis in the list validRomanNumerals are used to convert numbers bigger than 4000 so they must be there.

Asked By: user3122654

||

Answers:

Call romantoint after the for loop

 def checkIfRomanNumeral(numeral):
     """Controls that the userinput only contains valid roman numerals"""
     numeral = numeral.upper()
     validRomanNumerals = ["M", "D", "C", "L", "X", "V", "I"]
     for letters in numeral:
        if letters not in validRomanNumerals:
            print("Sorry that is not a valid roman numeral")
            return False
     romanToInt(numeral)
Answered By: rjv

Writing a converter from ints to Romans is a standard interview question. I once wrote the following bi-directional implementation (toString— decimal to Roman; parse — Roman to decimal). The implementaion saticifies a number of additional criteria on the representation of Roman numbers, which are not obligatory, but generally followed:

'''
Created on Feb 7, 2013

@author: olegs
'''

ROMAN_CONSTANTS = (
            ( "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX" ),
            ( "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC" ),
            ( "", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM" ),
            ( "", "M", "MM", "MMM", "",   "",  "-",  "",    "",     ""   ),
        )

ROMAN_SYMBOL_MAP = dict(I=1, V=5, X=10, L=50, C=100, D=500, M=1000)

CUTOFF = 4000
BIG_DEC = 2900
BIG_ROMAN = "MMCM"
ROMAN_NOUGHT = "nulla"

def digits(num):
    if num < 0:
        raise Exception('range error: negative numbers not supported')
    if num % 1 != 0.0:
        raise Exception('floating point numbers not supported')
    res = []
    while num > 0:
        res.append(num % 10)
        num //= 10
    return res

def toString(num, emptyZero=False):
    if num < CUTOFF:
        digitlist = digits(num)
        if digitlist:
            res = reversed([ ROMAN_CONSTANTS[order][digit] for order, digit in enumerate(digitlist) ])
            return "".join(res)
        else:
            return "" if emptyZero else ROMAN_NOUGHT 
    else:
        if num % 1 != 0.0:
            raise Exception('floating point numbers not supported')
        # For numbers over or equal the CUTOFF, the remainder of division by 2900
        # is represented as above, prepended with the multiples of MMCM (2900 in Roman),
        # which guarantees no more than 3 repetitive Ms.
        return BIG_ROMAN * (num // BIG_DEC) + toString(num % BIG_DEC, emptyZero=True)

def parse(numeral):
    numeral = numeral.upper()
    result = 0
    if numeral == ROMAN_NOUGHT.upper():
        return result
    lastVal = 0
    lastCount = 0
    subtraction = False
    for symbol in numeral[::-1]:
        value = ROMAN_SYMBOL_MAP.get(symbol)
        if not value:
            raise Exception('incorrect symbol')
        if lastVal == 0:
            lastCount = 1
            lastVal = value
        elif lastVal == value:
            lastCount += 1
            # exceptions
        else:
            result += (-1 if subtraction else 1) * lastVal * lastCount
            subtraction = lastVal > value
            lastCount = 1
            lastVal = value
    return result + (-1 if subtraction else 1) * lastVal * lastCount
Answered By: Oleg Sklyar
def checkIfRomanNumeral(numeral):
"""Controls that the userinput only contains valid roman numerals"""
    numeral = numeral.upper()
    validRomanNumerals = ["M", "D", "C", "L", "X", "V", "I", "(", ")"]
    valid = True
    for letters in numeral:
        if letters not in validRomanNumerals:
            print("Sorry that is not a valid roman numeral")
            valid = False
            break
    return valid

Returns a boolean whether the given
‘numeral’ is roman numeral or not.

Answered By: praveen

Apart from the design problems that have already been pointed out, I’d like to just answer the question why your for-loop doesn’t go through all the numerals

If the entries are considered valid by your code, then the loop goes into the elif clause
where it calls romanToInt(numeral)and then break. There’s your problem: break take that out.

Illustration: As soon as condition is met in this example, the loop will stop going through i in list

for i in list:
   # do something
   if condition:
       break # "Stop the innermost loop now!"
Answered By: Lmis

Instead of looping, you can convert both input and valid literals to sets and then substract them:

def checkIfRomanNumeral(numeral):
    numeral = {c for c in numeral.upper()}
    validRomanNumerals = {c for c in "MDCLXVI()"}
    return not numeral - validRomanNumerals

Returns True if numeral is valid, False otherwise. (Assuming that the empty string is valid)

Answered By: Hyperboreus

There is a way solution I built you can modify it easily to your needs and even get the number. My program is printing if the string from the user is a valid number, but you can easily change it to get the actual value.

def main():
    # YOUR CODE GOES HERE
    # Please take input and print output to standard input/output (stdin/stdout)
    # E.g. 'input()/raw_input()' for input & 'print' for output
    N = 1
    for i in range(N):
        romanStr = input() # your str
        units = ["I", "II", "III" , 'IV' , 'V', 'VI', 'VII', 'VIII', "IX"]
        tens = ['X', "XX", "XXX", 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC']
        hundreds = ['C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM']
        thousands = ['M', 'MM', 'MMM']
        allTypes = [units, tens, hundreds, thousands][::-1]
        isError = False
        lenRom = len(romanStr)
        copy = romanStr
        while len(copy) > 0 and len(allTypes) > 0:
            currentUnit = allTypes[0]
            # check units
            firstNum = copy[0]
            lastTwo = copy[:2]
            last3 = copy[:3]
            last4 = copy[:4]
            isLastTwo = lastTwo in currentUnit and len(lastTwo) == 2
            isLast3 = last3 in currentUnit and len(last3) == 3
            isLast4 = last4 in currentUnit and len(last4) == 4
            
            if (firstNum in currentUnit and not (isLastTwo or isLast3 or isLast4) ):
                copy = copy[1::]
                isError = False
            elif (isLastTwo and not (isLast3 or isLast4) ):
                copy = copy[2::]
                isError = False
            elif (isLast3 and not (isLast4) ):
                copy = copy[3::]
                isError = False
            elif (isLast4):
                copy = copy[4::]
                isError = False
            else:
                
                isError = True
                # copy = ''
            allTypes.pop(0)
        
        if (isError or len(copy) != 0 ):
            print("NO")
            return 0
        else:
            print("YES")
            return 1

    return 0

if __name__ == '__main__':
    main()
Answered By: shaked

I know the original poster was writing in Python, but I’ve taken Oleg’s answer from above, ported it to PHP, and corrected its deficits.

This PHP code should properly convert Roman numerals to numbers and should not allow invalid Roman numerals to be converted.

<?php
define('ROMAN_VERBOSE', true);

class RomanNumeral {

protected static $lookup2 = [
   'I' => 1,
   'V' => 5,
   'X' => 10,
   'L' => 50,
   'C' => 100,
   'D' => 500,
   'M' => 1000,
];

public static function toNumber($numeral) {
if (!$numeral || !is_string($numeral))
  throw new Exception("Invalid Roman numeral.");

$num = strtoupper(trim($numeral));
$nbr = 0;

if (ROMAN_VERBOSE === true)
  echo $num.PHP_EOL;

$lastVal = 0;
$lastCount = 0;
$lastGlyph = '';
$sub = false;

for ($x = 0; $x < strlen($num); $x++) {
  $val = self::$lookup2[$num[$x]] ?? 0;

  if (ROMAN_VERBOSE === true) {
    echo "********* ENTERING x = ".$x." *********".PHP_EOL;
    echo "Value: ".$val.PHP_EOL;
    echo "Last Count: ".$lastCount.PHP_EOL;
    echo "Last Value: ".$lastVal.PHP_EOL;
    echo "Glyph: ".$num[$x].PHP_EOL;
    echo "Last Glyph: ".$lastGlyph.PHP_EOL;
    echo "Current Number: ".$nbr.PHP_EOL;
    echo "Subtract: ".($sub !== false ? "Y" : "N").PHP_EOL;
    echo PHP_EOL;
  }

  if ($val == 0)
    throw new Exception("Invalid Roman numeral.");
  if ($lastVal == 0) {
    $lastCount = 1;
    $lastVal = $val;
    $lastGlyph = $num[$x];
  } else if ($lastVal == $val) {
    $lastCount += 1;
    $sub = false;
  } else {
    if ($num[$x] == 'V' || $num[$x] == 'L' || $num[$x] == 'D')
      $sub = false;
    $nbr += ($sub === true ? -1 : 1) * ($lastVal * $lastCount);
    $sub = ($lastVal > $val);
    if ($num[$x] == 'V' || $num[$x] == 'L' || $num[$x] == 'D')
      $sub = false;
    $lastCount = 1;
    $lastVal = $val;

    if ($sub) {
      if ($lastGlyph === 'I' && $num[$x] !== 'V' && $num[$x] !== 'X')
        throw new Exception("Invalid roman numeral.");
      if ($num[$x] !== 'L' && $num[$x] !== 'M' && $num[$x] !== 'C' && $lastGlyph == 'X')
        throw new Exception("Invalid roman numeral.");
    }
    $lastGlyph = $num[$x];
  }

  if (ROMAN_VERBOSE === true) {
    echo "********* LEAVING x = ".$x." *********".PHP_EOL;
    echo "Value: ".$val.PHP_EOL;
    echo "Last Count: ".$lastCount.PHP_EOL;
    echo "Last Value: ".$lastVal.PHP_EOL;
    echo "Glyph: ".$num[$x].PHP_EOL;
    echo "Last Glyph: ".$lastGlyph.PHP_EOL;
    echo "Current Number: ".$nbr.PHP_EOL;
    echo "Subtract: ".($sub !== false ? "Y" : "N").PHP_EOL;
    echo PHP_EOL;
  }
}
return $nbr + ($sub === true ? -1 : 1) * ($lastVal * $lastCount);
}
}

echo RomanNumeral::toNumber("DLXVIII").PHP_EOL;  // should be 568
echo RomanNumeral::toNumber("MMMCMXCIX").PHP_EOL; // should be 3999
echo RomanNumeral::toNumber("DLXIVIII").PHP_EOL;  // throws exception
?>
Answered By: Alex
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.