From 818bfab5d7ecf7b9340a408d5441de3ef6ae771e Mon Sep 17 00:00:00 2001 From: Tovi Jaeschke-Rogers Date: Thu, 3 Nov 2022 19:20:24 +1030 Subject: [PATCH] Add roman numeral to int converter (with validity checking) --- romanToInt.php | 165 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 romanToInt.php diff --git a/romanToInt.php b/romanToInt.php new file mode 100644 index 0000000..531d65f --- /dev/null +++ b/romanToInt.php @@ -0,0 +1,165 @@ + 1000, + 'D' => 500, + 'C' => 100, + 'L' => 50, + 'X' => 10, + 'V' => 5, + 'I' => 1, + ]; + + /** + * @param mixed $str + * @static + * @access public + * @return int + */ + public static function romanToInt(string $str, $checkInvalid = false): int + { + if ($checkInvalid && !self::isRomanValid($str)) { + throw new RuntimeException('Given number is invalid'); + } + + $total = 0; + + $letters = str_split(strtoupper($str)); + + for ($i = 0; $i < count($letters); $i++) { + $letterVal = self::romanToIntMap[$letters[$i]]; + $nextLetterVal = isset($letters[$i + 1]) ? self::romanToIntMap[$letters[$i + 1]] : 0; + + if ($letterVal < $nextLetterVal) { + $total -= $letterVal; + continue; + } + + $total += $letterVal; + } + + return $total; + } + + /** + * @param string $str + * @static + * @access public + * @return bool + */ + public static function isRomanValid(string $str): bool + { + // If an empty string is provided, return false, + // as the romans did not have a concept of 0 + // (btw, they called it "nulla") + if ($str === '') { + return false; + } + + $letters = str_split(strtoupper($str)); + + $currentLetter = $letters[0]; + $count = 0; + + for ($i = 1; $i < count($letters); $i++) { + + // Ensure V, L, and D are not repeated, and + // if I, X, and C are the only subtractive operators + $letterVal = self::romanToIntMap[$currentLetter]; + $nextLetterVal = isset($letters[$i]) ? self::romanToIntMap[$letters[$i]] : 0; + $currentVal = substr($letterVal, 0, 1); + if ($letterVal <= $nextLetterVal && ($currentVal === '5' || $nextLetterVal === '1000')) { + return false; + } + + // Check if a number is repeated more than 3 times + $letterSameAsNext = $currentLetter === $letters[$i]; + $count = $letterSameAsNext ? $count + 1 : 0; + if ($count >= 3) { + return false; + } + + + $currentLetter = $letters[$i] ?? ''; + } + + return true; + } + + /** + * @param mixed $str + * @param mixed $expectedVal + * @static + * @access public + * @return void + */ + public static function testRomanToInt(string $str, int $expectedVal): void + { + $val = self::romanToInt($str); + if ($val != $expectedVal) { + echo sprintf( + 'Failed: The value of \'%d\' did not match the expected value of \'%d\' for \'%s\'' . PHP_EOL, + $val, + $expectedVal, + $str + ); + return; + } + + echo sprintf( + 'Succeeded: The value of \'%d\' matched the expected value of \'%s\'' . PHP_EOL, + $val, + $str + ); + } + + /** + * @param string $str + * @param bool $expectedValid + * @static + * @access public + * @return void + */ + public static function testIsRomanValid(string $str, bool $expectedValid) { + $val = self::isRomanValid($str); + if ($val != $expectedValid) { + echo sprintf( + 'Failed: The value of \'%s\' did not match the expected value of \'%s\' for \'%s\'' . PHP_EOL, + $val ? 'True' : 'False', + $expectedValid ? 'True' : 'False', + $str + ); + return; + } + + echo sprintf( + 'Succeeded: The value of \'%d\' matched the expected value of \'%s\'' . PHP_EOL, + $val ? 'True' : 'False', + $str + ); + + } +} + +// Tests +Solution::testRomanToInt('I', 1); +Solution::testRomanToInt('III', 3); +Solution::testRomanToInt('IV', 4); +Solution::testRomanToInt('V', 5); +Solution::testRomanToInt('VI', 6); +Solution::testRomanToInt('IX', 9); +Solution::testRomanToInt('X', 10); +Solution::testRomanToInt('XI', 11); +Solution::testRomanToInt('LVIII', 58); +Solution::testRomanToInt('MCMXCIV', 1994); + +// Validity tests +Solution::testIsRomanValid('III', true); +Solution::testIsRomanValid('IIII', false); +Solution::testIsRomanValid('VI', true); +Solution::testIsRomanValid('VV', false); +Solution::testIsRomanValid('LL', false); +Solution::testIsRomanValid('VL', false); +Solution::testIsRomanValid('DD', false); +Solution::testIsRomanValid('MCMXCIV', true);