|
|
- <?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);
|