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

  1. <?php
  2. class Solution {
  3. const romanToIntMap = [
  4. 'M' => 1000,
  5. 'D' => 500,
  6. 'C' => 100,
  7. 'L' => 50,
  8. 'X' => 10,
  9. 'V' => 5,
  10. 'I' => 1,
  11. ];
  12. /**
  13. * @param mixed $str
  14. * @static
  15. * @access public
  16. * @return int
  17. */
  18. public static function romanToInt(string $str, $checkInvalid = false): int
  19. {
  20. if ($checkInvalid && !self::isRomanValid($str)) {
  21. throw new RuntimeException('Given number is invalid');
  22. }
  23. $total = 0;
  24. $letters = str_split(strtoupper($str));
  25. for ($i = 0; $i < count($letters); $i++) {
  26. $letterVal = self::romanToIntMap[$letters[$i]];
  27. $nextLetterVal = isset($letters[$i + 1]) ? self::romanToIntMap[$letters[$i + 1]] : 0;
  28. if ($letterVal < $nextLetterVal) {
  29. $total -= $letterVal;
  30. continue;
  31. }
  32. $total += $letterVal;
  33. }
  34. return $total;
  35. }
  36. /**
  37. * @param string $str
  38. * @static
  39. * @access public
  40. * @return bool
  41. */
  42. public static function isRomanValid(string $str): bool
  43. {
  44. // If an empty string is provided, return false,
  45. // as the romans did not have a concept of 0
  46. // (btw, they called it "nulla")
  47. if ($str === '') {
  48. return false;
  49. }
  50. $letters = str_split(strtoupper($str));
  51. $currentLetter = $letters[0];
  52. $count = 0;
  53. for ($i = 1; $i < count($letters); $i++) {
  54. // Ensure V, L, and D are not repeated, and
  55. // if I, X, and C are the only subtractive operators
  56. $letterVal = self::romanToIntMap[$currentLetter];
  57. $nextLetterVal = isset($letters[$i]) ? self::romanToIntMap[$letters[$i]] : 0;
  58. $currentVal = substr($letterVal, 0, 1);
  59. if ($letterVal <= $nextLetterVal && ($currentVal === '5' || $nextLetterVal === '1000')) {
  60. return false;
  61. }
  62. // Check if a number is repeated more than 3 times
  63. $letterSameAsNext = $currentLetter === $letters[$i];
  64. $count = $letterSameAsNext ? $count + 1 : 0;
  65. if ($count >= 3) {
  66. return false;
  67. }
  68. $currentLetter = $letters[$i] ?? '';
  69. }
  70. return true;
  71. }
  72. /**
  73. * @param mixed $str
  74. * @param mixed $expectedVal
  75. * @static
  76. * @access public
  77. * @return void
  78. */
  79. public static function testRomanToInt(string $str, int $expectedVal): void
  80. {
  81. $val = self::romanToInt($str);
  82. if ($val != $expectedVal) {
  83. echo sprintf(
  84. 'Failed: The value of \'%d\' did not match the expected value of \'%d\' for \'%s\'' . PHP_EOL,
  85. $val,
  86. $expectedVal,
  87. $str
  88. );
  89. return;
  90. }
  91. echo sprintf(
  92. 'Succeeded: The value of \'%d\' matched the expected value of \'%s\'' . PHP_EOL,
  93. $val,
  94. $str
  95. );
  96. }
  97. /**
  98. * @param string $str
  99. * @param bool $expectedValid
  100. * @static
  101. * @access public
  102. * @return void
  103. */
  104. public static function testIsRomanValid(string $str, bool $expectedValid) {
  105. $val = self::isRomanValid($str);
  106. if ($val != $expectedValid) {
  107. echo sprintf(
  108. 'Failed: The value of \'%s\' did not match the expected value of \'%s\' for \'%s\'' . PHP_EOL,
  109. $val ? 'True' : 'False',
  110. $expectedValid ? 'True' : 'False',
  111. $str
  112. );
  113. return;
  114. }
  115. echo sprintf(
  116. 'Succeeded: The value of \'%s\' matched the expected value of \'%s\'' . PHP_EOL,
  117. $val ? 'True' : 'False',
  118. $str
  119. );
  120. }
  121. }
  122. // Tests
  123. Solution::testRomanToInt('I', 1);
  124. Solution::testRomanToInt('III', 3);
  125. Solution::testRomanToInt('IV', 4);
  126. Solution::testRomanToInt('V', 5);
  127. Solution::testRomanToInt('VI', 6);
  128. Solution::testRomanToInt('IX', 9);
  129. Solution::testRomanToInt('X', 10);
  130. Solution::testRomanToInt('XI', 11);
  131. Solution::testRomanToInt('LVIII', 58);
  132. Solution::testRomanToInt('MCMXCIV', 1994);
  133. // Validity tests
  134. Solution::testIsRomanValid('III', true);
  135. Solution::testIsRomanValid('IIII', false);
  136. Solution::testIsRomanValid('VI', true);
  137. Solution::testIsRomanValid('VV', false);
  138. Solution::testIsRomanValid('LL', false);
  139. Solution::testIsRomanValid('VL', false);
  140. Solution::testIsRomanValid('DD', false);
  141. Solution::testIsRomanValid('MCMXCIV', true);