Differ.php 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. <?php declare(strict_types=1);
  2. namespace PhpParser\Internal;
  3. /**
  4. * Implements the Myers diff algorithm.
  5. *
  6. * Myers, Eugene W. "An O (ND) difference algorithm and its variations."
  7. * Algorithmica 1.1 (1986): 251-266.
  8. *
  9. * @internal
  10. */
  11. class Differ
  12. {
  13. private $isEqual;
  14. /**
  15. * Create differ over the given equality relation.
  16. *
  17. * @param callable $isEqual Equality relation with signature function($a, $b) : bool
  18. */
  19. public function __construct(callable $isEqual) {
  20. $this->isEqual = $isEqual;
  21. }
  22. /**
  23. * Calculate diff (edit script) from $old to $new.
  24. *
  25. * @param array $old Original array
  26. * @param array $new New array
  27. *
  28. * @return DiffElem[] Diff (edit script)
  29. */
  30. public function diff(array $old, array $new) {
  31. list($trace, $x, $y) = $this->calculateTrace($old, $new);
  32. return $this->extractDiff($trace, $x, $y, $old, $new);
  33. }
  34. /**
  35. * Calculate diff, including "replace" operations.
  36. *
  37. * If a sequence of remove operations is followed by the same number of add operations, these
  38. * will be coalesced into replace operations.
  39. *
  40. * @param array $old Original array
  41. * @param array $new New array
  42. *
  43. * @return DiffElem[] Diff (edit script), including replace operations
  44. */
  45. public function diffWithReplacements(array $old, array $new) {
  46. return $this->coalesceReplacements($this->diff($old, $new));
  47. }
  48. private function calculateTrace(array $a, array $b) {
  49. $n = \count($a);
  50. $m = \count($b);
  51. $max = $n + $m;
  52. $v = [1 => 0];
  53. $trace = [];
  54. for ($d = 0; $d <= $max; $d++) {
  55. $trace[] = $v;
  56. for ($k = -$d; $k <= $d; $k += 2) {
  57. if ($k === -$d || ($k !== $d && $v[$k-1] < $v[$k+1])) {
  58. $x = $v[$k+1];
  59. } else {
  60. $x = $v[$k-1] + 1;
  61. }
  62. $y = $x - $k;
  63. while ($x < $n && $y < $m && ($this->isEqual)($a[$x], $b[$y])) {
  64. $x++;
  65. $y++;
  66. }
  67. $v[$k] = $x;
  68. if ($x >= $n && $y >= $m) {
  69. return [$trace, $x, $y];
  70. }
  71. }
  72. }
  73. throw new \Exception('Should not happen');
  74. }
  75. private function extractDiff(array $trace, int $x, int $y, array $a, array $b) {
  76. $result = [];
  77. for ($d = \count($trace) - 1; $d >= 0; $d--) {
  78. $v = $trace[$d];
  79. $k = $x - $y;
  80. if ($k === -$d || ($k !== $d && $v[$k-1] < $v[$k+1])) {
  81. $prevK = $k + 1;
  82. } else {
  83. $prevK = $k - 1;
  84. }
  85. $prevX = $v[$prevK];
  86. $prevY = $prevX - $prevK;
  87. while ($x > $prevX && $y > $prevY) {
  88. $result[] = new DiffElem(DiffElem::TYPE_KEEP, $a[$x-1], $b[$y-1]);
  89. $x--;
  90. $y--;
  91. }
  92. if ($d === 0) {
  93. break;
  94. }
  95. while ($x > $prevX) {
  96. $result[] = new DiffElem(DiffElem::TYPE_REMOVE, $a[$x-1], null);
  97. $x--;
  98. }
  99. while ($y > $prevY) {
  100. $result[] = new DiffElem(DiffElem::TYPE_ADD, null, $b[$y-1]);
  101. $y--;
  102. }
  103. }
  104. return array_reverse($result);
  105. }
  106. /**
  107. * Coalesce equal-length sequences of remove+add into a replace operation.
  108. *
  109. * @param DiffElem[] $diff
  110. * @return DiffElem[]
  111. */
  112. private function coalesceReplacements(array $diff) {
  113. $newDiff = [];
  114. $c = \count($diff);
  115. for ($i = 0; $i < $c; $i++) {
  116. $diffType = $diff[$i]->type;
  117. if ($diffType !== DiffElem::TYPE_REMOVE) {
  118. $newDiff[] = $diff[$i];
  119. continue;
  120. }
  121. $j = $i;
  122. while ($j < $c && $diff[$j]->type === DiffElem::TYPE_REMOVE) {
  123. $j++;
  124. }
  125. $k = $j;
  126. while ($k < $c && $diff[$k]->type === DiffElem::TYPE_ADD) {
  127. $k++;
  128. }
  129. if ($j - $i === $k - $j) {
  130. $len = $j - $i;
  131. for ($n = 0; $n < $len; $n++) {
  132. $newDiff[] = new DiffElem(
  133. DiffElem::TYPE_REPLACE, $diff[$i + $n]->old, $diff[$j + $n]->new
  134. );
  135. }
  136. } else {
  137. for (; $i < $k; $i++) {
  138. $newDiff[] = $diff[$i];
  139. }
  140. }
  141. $i = $k - 1;
  142. }
  143. return $newDiff;
  144. }
  145. }