arrayDiff.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. function diff(oldArr, newArr, equals) {
  2. if (!equals) {
  3. equals = function (a, b) {
  4. return a === b;
  5. };
  6. }
  7. oldArr = oldArr.slice();
  8. newArr = newArr.slice();
  9. var newLen = newArr.length;
  10. var oldLen = oldArr.length;
  11. var editLength = 1;
  12. var maxEditLength = newLen + oldLen;
  13. var bestPath = [{ newPos: -1, components: [] }];
  14. var oldPos = extractCommon(bestPath[0], newArr, oldArr, 0, equals);
  15. if (!oldLen
  16. || !newLen
  17. || (bestPath[0].newPos + 1 >= newLen && oldPos + 1 >= oldLen)) {
  18. var indices = [];
  19. var allCleared = !newLen && oldLen > 0;
  20. var allCreated = !oldLen && newLen > 0;
  21. for (var i = 0; i < (allCleared ? oldArr : newArr).length; i++) {
  22. indices.push(i);
  23. }
  24. return [{
  25. indices: indices,
  26. count: indices.length,
  27. added: allCreated,
  28. removed: allCleared
  29. }];
  30. }
  31. function execEditLength() {
  32. for (var diagonalPath = -1 * editLength; diagonalPath <= editLength; diagonalPath += 2) {
  33. var basePath;
  34. var addPath = bestPath[diagonalPath - 1];
  35. var removePath = bestPath[diagonalPath + 1];
  36. var oldPos = (removePath ? removePath.newPos : 0) - diagonalPath;
  37. if (addPath) {
  38. bestPath[diagonalPath - 1] = undefined;
  39. }
  40. var canAdd = addPath && addPath.newPos + 1 < newLen;
  41. var canRemove = removePath && 0 <= oldPos && oldPos < oldLen;
  42. if (!canAdd && !canRemove) {
  43. bestPath[diagonalPath] = undefined;
  44. continue;
  45. }
  46. if (!canAdd || (canRemove && addPath.newPos < removePath.newPos)) {
  47. basePath = clonePath(removePath);
  48. pushComponent(basePath.components, false, true);
  49. }
  50. else {
  51. basePath = addPath;
  52. basePath.newPos++;
  53. pushComponent(basePath.components, true, false);
  54. }
  55. oldPos = extractCommon(basePath, newArr, oldArr, diagonalPath, equals);
  56. if (basePath.newPos + 1 >= newLen && oldPos + 1 >= oldLen) {
  57. return buildValues(basePath.components);
  58. }
  59. else {
  60. bestPath[diagonalPath] = basePath;
  61. }
  62. }
  63. editLength++;
  64. }
  65. while (editLength <= maxEditLength) {
  66. var ret = execEditLength();
  67. if (ret) {
  68. return ret;
  69. }
  70. }
  71. }
  72. function extractCommon(basePath, newArr, oldArr, diagonalPath, equals) {
  73. var newLen = newArr.length;
  74. var oldLen = oldArr.length;
  75. var newPos = basePath.newPos;
  76. var oldPos = newPos - diagonalPath;
  77. var commonCount = 0;
  78. while (newPos + 1 < newLen && oldPos + 1 < oldLen && equals(newArr[newPos + 1], oldArr[oldPos + 1])) {
  79. newPos++;
  80. oldPos++;
  81. commonCount++;
  82. }
  83. if (commonCount) {
  84. basePath.components.push({
  85. count: commonCount,
  86. added: false,
  87. removed: false,
  88. indices: []
  89. });
  90. }
  91. basePath.newPos = newPos;
  92. return oldPos;
  93. }
  94. function pushComponent(components, added, removed) {
  95. var last = components[components.length - 1];
  96. if (last && last.added === added && last.removed === removed) {
  97. components[components.length - 1] = {
  98. count: last.count + 1,
  99. added: added,
  100. removed: removed,
  101. indices: []
  102. };
  103. }
  104. else {
  105. components.push({
  106. count: 1,
  107. added: added,
  108. removed: removed,
  109. indices: []
  110. });
  111. }
  112. }
  113. function buildValues(components) {
  114. var componentPos = 0;
  115. var componentLen = components.length;
  116. var newPos = 0;
  117. var oldPos = 0;
  118. for (; componentPos < componentLen; componentPos++) {
  119. var component = components[componentPos];
  120. if (!component.removed) {
  121. var indices = [];
  122. for (var i = newPos; i < newPos + component.count; i++) {
  123. indices.push(i);
  124. }
  125. component.indices = indices;
  126. newPos += component.count;
  127. if (!component.added) {
  128. oldPos += component.count;
  129. }
  130. }
  131. else {
  132. for (var i = oldPos; i < oldPos + component.count; i++) {
  133. component.indices.push(i);
  134. }
  135. oldPos += component.count;
  136. }
  137. }
  138. return components;
  139. }
  140. function clonePath(path) {
  141. return { newPos: path.newPos, components: path.components.slice(0) };
  142. }
  143. export default function arrayDiff(oldArr, newArr, equal) {
  144. return diff(oldArr, newArr, equal);
  145. }