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