MemoryEfficientLongestCommonSubsequenceCalculator.php 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. <?php declare(strict_types=1);
  2. /*
  3. * This file is part of sebastian/diff.
  4. *
  5. * (c) Sebastian Bergmann <sebastian@phpunit.de>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace SebastianBergmann\Diff;
  11. use function array_fill;
  12. use function array_merge;
  13. use function array_reverse;
  14. use function array_slice;
  15. use function count;
  16. use function in_array;
  17. use function max;
  18. final class MemoryEfficientLongestCommonSubsequenceCalculator implements LongestCommonSubsequenceCalculator
  19. {
  20. /**
  21. * @inheritDoc
  22. */
  23. public function calculate(array $from, array $to): array
  24. {
  25. $cFrom = count($from);
  26. $cTo = count($to);
  27. if ($cFrom === 0) {
  28. return [];
  29. }
  30. if ($cFrom === 1) {
  31. if (in_array($from[0], $to, true)) {
  32. return [$from[0]];
  33. }
  34. return [];
  35. }
  36. $i = (int) ($cFrom / 2);
  37. $fromStart = array_slice($from, 0, $i);
  38. $fromEnd = array_slice($from, $i);
  39. $llB = $this->length($fromStart, $to);
  40. $llE = $this->length(array_reverse($fromEnd), array_reverse($to));
  41. $jMax = 0;
  42. $max = 0;
  43. for ($j = 0; $j <= $cTo; $j++) {
  44. $m = $llB[$j] + $llE[$cTo - $j];
  45. if ($m >= $max) {
  46. $max = $m;
  47. $jMax = $j;
  48. }
  49. }
  50. $toStart = array_slice($to, 0, $jMax);
  51. $toEnd = array_slice($to, $jMax);
  52. return array_merge(
  53. $this->calculate($fromStart, $toStart),
  54. $this->calculate($fromEnd, $toEnd),
  55. );
  56. }
  57. private function length(array $from, array $to): array
  58. {
  59. $current = array_fill(0, count($to) + 1, 0);
  60. $cFrom = count($from);
  61. $cTo = count($to);
  62. for ($i = 0; $i < $cFrom; $i++) {
  63. $prev = $current;
  64. for ($j = 0; $j < $cTo; $j++) {
  65. if ($from[$i] === $to[$j]) {
  66. $current[$j + 1] = $prev[$j] + 1;
  67. } else {
  68. /**
  69. * @noinspection PhpConditionCanBeReplacedWithMinMaxCallInspection
  70. *
  71. * We do not use max() here to avoid the function call overhead
  72. */
  73. if ($current[$j] > $prev[$j + 1]) {
  74. $current[$j + 1] = $current[$j];
  75. } else {
  76. $current[$j + 1] = $prev[$j + 1];
  77. }
  78. }
  79. }
  80. }
  81. return $current;
  82. }
  83. }