| @ -0,0 +1,165 @@ | |||
| <?php | |||
| class Solution { | |||
| const romanToIntMap = [ | |||
| 'M' => 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); | |||