You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

165 lines
4.5 KiB

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