arrayDiff.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. // Hirschberg's algorithm
  2. // http://en.wikipedia.org/wiki/Hirschberg%27s_algorithm
  3. /**
  4. * @module zrender/core/arrayDiff
  5. * @author Yi Shen
  6. */
  7. function defaultCompareFunc(a, b) {
  8. return a === b;
  9. }
  10. function createItem(cmd, idx, idx1) {
  11. var res = {
  12. // cmd explanation
  13. // '=': not change
  14. // '^': replace with a new item in second array. Unused temporary
  15. // '+': add a new item of second array
  16. // '-': del item in first array
  17. cmd: cmd,
  18. // Value index, use index in the first array
  19. // Except '+'. Adding a new item needs value in the second array
  20. idx: idx
  21. }; // Replace need to know both two indices
  22. // if (cmd === '^') {
  23. // res.idx1 = idx1;
  24. // }
  25. if (cmd === '=') {
  26. res.idx1 = idx1;
  27. }
  28. return res;
  29. }
  30. function append(out, cmd, idx, idx1) {
  31. out.push(createItem(cmd, idx, idx1));
  32. }
  33. var abs = Math.abs; // Needleman-Wunsch score
  34. function score(arr0, arr1, i0, i1, j0, j1, equal, memo) {
  35. var last;
  36. var invM = i0 > i1;
  37. var invN = j0 > j1;
  38. var m = abs(i1 - i0);
  39. var n = abs(j1 - j0);
  40. var i;
  41. var j;
  42. for (i = 0; i <= m; i++) {
  43. for (j = 0; j <= n; j++) {
  44. if (i === 0) {
  45. memo[j] = j;
  46. } else if (j === 0) {
  47. last = memo[j];
  48. memo[j] = i;
  49. } else {
  50. // memo[i-1][j-1] + same(arr0[i-1], arr1[j-1]) ? 0 : 1
  51. // Retained or replace
  52. var val0 = arr0[invM ? i0 - i : i - 1 + i0];
  53. var val1 = arr1[invN ? j0 - j : j - 1 + j0]; // Because replace is add after remove actually
  54. // It has a higher score than removing or adding
  55. // TODO custom score function
  56. var score0 = last + (equal(val0, val1) ? 0 : 2); // memo[i-1][j] + 1
  57. // Remove arr0[i-1]
  58. var score1 = memo[j] + 1; // memo[i][j-1] + 1
  59. // Add arr1[j-1]
  60. var score2 = memo[j - 1] + 1;
  61. last = memo[j];
  62. memo[j] = score0 < score1 ? score0 : score1;
  63. score2 < memo[j] && (memo[j] = score2); // Math min of three parameters seems slow
  64. // memo[j] = Math.min(score0, score1, score2);
  65. }
  66. }
  67. }
  68. return memo;
  69. }
  70. function hirschberg(arr0, arr1, i0, i1, j0, j1, equal, score0, score1) {
  71. var out = [];
  72. var len0 = i1 - i0;
  73. var len1 = j1 - j0;
  74. var i;
  75. var j;
  76. if (!len0) {
  77. for (j = 0; j < len1; j++) {
  78. append(out, '+', j + j0);
  79. }
  80. } else if (!len1) {
  81. for (i = 0; i < len0; i++) {
  82. append(out, '-', i + i0);
  83. }
  84. } else if (len0 === 1) {
  85. var a = arr0[i0];
  86. var matched = false;
  87. for (j = 0; j < len1; j++) {
  88. if (equal(a, arr1[j + j0]) && !matched) {
  89. matched = true; // Equal and update use the index in first array
  90. append(out, '=', i0, j + j0);
  91. } else {
  92. // if (j === len1 - 1 && ! matched) {
  93. // append(out, '^', i0, j + j0);
  94. // }
  95. // else {
  96. append(out, '+', j + j0); // }
  97. }
  98. }
  99. if (!matched) {
  100. append(out, '-', i0);
  101. }
  102. } else if (len1 === 1) {
  103. var b = arr1[j0];
  104. var matched = false;
  105. for (i = 0; i < len0; i++) {
  106. if (equal(b, arr0[i + i0]) && !matched) {
  107. matched = true;
  108. append(out, '=', i + i0, j0);
  109. } else {
  110. // if (i === len0 - 1 && ! matched) {
  111. // append(out, '^', i + i0, j0);
  112. // }
  113. // else {
  114. append(out, '-', i + i0); // }
  115. }
  116. }
  117. if (!matched) {
  118. append(out, '+', j0);
  119. }
  120. } else {
  121. var imid = (len0 / 2 | 0) + i0;
  122. score(arr0, arr1, i0, imid, j0, j1, equal, score0);
  123. score(arr0, arr1, i1, imid + 1, j1, j0, equal, score1);
  124. var min = Infinity;
  125. var jmid = 0;
  126. var sum;
  127. for (j = 0; j <= len1; j++) {
  128. sum = score0[j] + score1[len1 - j];
  129. if (sum < min) {
  130. min = sum;
  131. jmid = j;
  132. }
  133. }
  134. jmid += j0;
  135. out = hirschberg(arr0, arr1, i0, imid, j0, jmid, equal, score0, score1);
  136. var out1 = hirschberg(arr0, arr1, imid, i1, jmid, j1, equal, score0, score1); // Concat
  137. for (i = 0; i < out1.length; i++) {
  138. out.push(out1[i]);
  139. }
  140. }
  141. return out;
  142. }
  143. function arrayDiff(arr0, arr1, equal) {
  144. equal = equal || defaultCompareFunc; // Remove the common head and tail
  145. var i;
  146. var j;
  147. var len0 = arr0.length;
  148. var len1 = arr1.length;
  149. var lenMin = Math.min(len0, len1);
  150. var head = [];
  151. for (i = 0; i < lenMin; i++) {
  152. if (!equal(arr0[i], arr1[i])) {
  153. break;
  154. }
  155. append(head, '=', i, i);
  156. }
  157. for (j = 0; j < lenMin; j++) {
  158. if (!equal(arr0[len0 - j - 1], arr1[len1 - j - 1])) {
  159. break;
  160. }
  161. }
  162. if (len0 - j >= i || len1 - j >= i) {
  163. var middle = hirschberg(arr0, arr1, i, len0 - j, i, len1 - j, equal, [], []);
  164. for (i = 0; i < middle.length; i++) {
  165. head.push(middle[i]);
  166. }
  167. for (i = 0; i < j; i++) {
  168. append(head, '=', len0 - j + i, len1 - j + i);
  169. }
  170. }
  171. return head;
  172. }
  173. var _default = arrayDiff;
  174. module.exports = _default;