palindrome.php 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. <?php
  2. /* Algorithm for finding (sub)palindromes in phrase
  3. Note: if there are spaces, they will be ignored
  4. and the string will be treated as if they weren't there.
  5. Also, all duplicates will not be included in result.
  6. Manacher's Algorithm used here.
  7. */
  8. mb_internal_encoding("UTF-8"); // UTF-8 support
  9. class Palindrome {
  10. public $subpalindromes;
  11. public $success;
  12. public $fail;
  13. function __construct() {
  14. $this->subpalindromes = function ($phrase) {
  15. $phrase = mb_strtolower($phrase); // mb_* functions used to support UTF-8 encoding.
  16. $phrase = mb_ereg_replace(" ", "", $phrase); // remove all spaces
  17. $manacher_subpalindromes = self::manacher($phrase); // run Manacher's algorithm to find odd and even subpalindromes
  18. $odd_subpalindromes = $manacher_subpalindromes[0];
  19. $even_subpalindromes = $manacher_subpalindromes[1];
  20. $palindromes = [];
  21. for ($i = 0; $i < mb_strlen($phrase); $i++) {
  22. if ($odd_subpalindromes[$i] > 1) { // ignore palindromes which consits of one character
  23. $palindromeLength = $odd_subpalindromes[$i]; // $odd_subpalindromes represents array of lengths of biggest palindromes, where index is the index of the character, which is the center of this palindrome
  24. while ($palindromeLength > 1) {
  25. $palindromes[] = mb_substr($phrase, $i - $palindromeLength + 1, $palindromeLength*2 - 1);
  26. $palindromeLength--;
  27. }
  28. }
  29. // same as previous with specific changes
  30. if ($even_subpalindromes[$i] > 0) {
  31. $palindromeLength = $even_subpalindromes[$i];
  32. while ($palindromeLength > 0) {
  33. $palindromes[] = mb_substr($phrase, $i - $palindromeLength, $palindromeLength*2);
  34. $palindromeLength--;
  35. }
  36. }
  37. }
  38. return array_values(array_unique($palindromes));
  39. };
  40. /* Used for formatting correct success message
  41. depending on the number of words found.
  42. */
  43. $this->success = function ($n) {
  44. $n_1 = $n % 100;
  45. if ($n_1 > 19) {
  46. $n_1 = $n_1 % 10;
  47. }
  48. $palindromeWordForm = "палиндром";
  49. $foundWordForm = "Найдено";
  50. if ($n_1 >= 2 && $n_1 <= 4) {
  51. $palindromeWordForm = "палиндрома";
  52. } else if ($n_1 == 1) {
  53. $palindromeWordForm = "палиндром";
  54. $foundWordForm = "Найден";
  55. } else {
  56. $palindromeWordForm = "палиндромов";
  57. }
  58. return $foundWordForm." ".strval($n)." ".$palindromeWordForm;
  59. };
  60. $this->fail = "Не найдено ни одного палиндрома";
  61. }
  62. /* Manacher's algorithm.
  63. Time complexity: O(n)
  64. Space complexity: O(1)
  65. */
  66. private static function manacher($str) {
  67. $n = mb_strlen($str);
  68. $odd = array_fill(0, $n, 0); // odd subpalindromes
  69. $even = array_fill(0, $n, 0); // even subpalindromes
  70. /* Odd subpalindromes */
  71. $l = 0; // left edge of current rightest palindrome
  72. $r = -1; // right edge of current rightst palindrome
  73. for ($i = 0; $i < $n; $i++) {
  74. $k = $i > $r ? 1 : min($odd[$l + $r - $i], $r - $i + 1); // k - known via previous steps palindrome center offset (guaranteed biggest palindrome with center in current position [i-k, i+k]
  75. while ($i + $k < $n && $i - $k >= 0 && mb_substr($str, $i - $k, 1) == mb_substr($str, $i + $k, 1)) {
  76. $k++; // increment offset if we are still between 0 and string length and mirrored elements are equal
  77. }
  78. $odd[$i] = $k; // here we know max palindrome size with center in current position (i)
  79. if ($i + $k - 1 > $r) { // if right edge of current palindrome is righter than right edge of the previous rightest palindrome, renew l and r
  80. $l = $i - $k + 1;
  81. $r = $i + $k - 1;
  82. }
  83. }
  84. /* Even subpalindromes
  85. a bit modified previous algorithm (for odd subpalindromes)
  86. */
  87. $l = 0;
  88. $r = -1;
  89. for ($i = 0; $i < $n; $i++) {
  90. $k = $i > $r ? 0 : min($even[$l + $r - $i + 1], $r - $i + 1);
  91. while ($i + $k < $n && $i - $k - 1 >= 0 && mb_substr($str, $i + $k, 1) == mb_substr($str, $i - $k - 1, 1)) {
  92. $k++;
  93. }
  94. $even[$i] = $k;
  95. if ($i + $k - 1 > $r) {
  96. $l = $i - $k;
  97. $r = $i + $k - 1;
  98. }
  99. }
  100. return [$odd, $even];
  101. }
  102. }