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 \'%s\' 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);