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