diff options
Diffstat (limited to 'chromium/third_party/catapult/third_party/polymer3/bower_components/polymer/lib/utils/array-splice.js')
-rw-r--r-- | chromium/third_party/catapult/third_party/polymer3/bower_components/polymer/lib/utils/array-splice.js | 299 |
1 files changed, 299 insertions, 0 deletions
diff --git a/chromium/third_party/catapult/third_party/polymer3/bower_components/polymer/lib/utils/array-splice.js b/chromium/third_party/catapult/third_party/polymer3/bower_components/polymer/lib/utils/array-splice.js new file mode 100644 index 00000000000..77ccb1af5c1 --- /dev/null +++ b/chromium/third_party/catapult/third_party/polymer3/bower_components/polymer/lib/utils/array-splice.js @@ -0,0 +1,299 @@ +/** +@license +Copyright (c) 2017 The Polymer Project Authors. All rights reserved. +This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt +The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt +The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt +Code distributed by Google as part of the polymer project is also +subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt +*/ +import './boot.js'; + +function newSplice(index, removed, addedCount) { + return { + index: index, + removed: removed, + addedCount: addedCount + }; +} + +const EDIT_LEAVE = 0; +const EDIT_UPDATE = 1; +const EDIT_ADD = 2; +const EDIT_DELETE = 3; + +// Note: This function is *based* on the computation of the Levenshtein +// "edit" distance. The one change is that "updates" are treated as two +// edits - not one. With Array splices, an update is really a delete +// followed by an add. By retaining this, we optimize for "keeping" the +// maximum array items in the original array. For example: +// +// 'xxxx123' -> '123yyyy' +// +// With 1-edit updates, the shortest path would be just to update all seven +// characters. With 2-edit updates, we delete 4, leave 3, and add 4. This +// leaves the substring '123' intact. +function calcEditDistances(current, currentStart, currentEnd, + old, oldStart, oldEnd) { + // "Deletion" columns + let rowCount = oldEnd - oldStart + 1; + let columnCount = currentEnd - currentStart + 1; + let distances = new Array(rowCount); + + // "Addition" rows. Initialize null column. + for (let i = 0; i < rowCount; i++) { + distances[i] = new Array(columnCount); + distances[i][0] = i; + } + + // Initialize null row + for (let j = 0; j < columnCount; j++) + distances[0][j] = j; + + for (let i = 1; i < rowCount; i++) { + for (let j = 1; j < columnCount; j++) { + if (equals(current[currentStart + j - 1], old[oldStart + i - 1])) + distances[i][j] = distances[i - 1][j - 1]; + else { + let north = distances[i - 1][j] + 1; + let west = distances[i][j - 1] + 1; + distances[i][j] = north < west ? north : west; + } + } + } + + return distances; +} + +// This starts at the final weight, and walks "backward" by finding +// the minimum previous weight recursively until the origin of the weight +// matrix. +function spliceOperationsFromEditDistances(distances) { + let i = distances.length - 1; + let j = distances[0].length - 1; + let current = distances[i][j]; + let edits = []; + while (i > 0 || j > 0) { + if (i == 0) { + edits.push(EDIT_ADD); + j--; + continue; + } + if (j == 0) { + edits.push(EDIT_DELETE); + i--; + continue; + } + let northWest = distances[i - 1][j - 1]; + let west = distances[i - 1][j]; + let north = distances[i][j - 1]; + + let min; + if (west < north) + min = west < northWest ? west : northWest; + else + min = north < northWest ? north : northWest; + + if (min == northWest) { + if (northWest == current) { + edits.push(EDIT_LEAVE); + } else { + edits.push(EDIT_UPDATE); + current = northWest; + } + i--; + j--; + } else if (min == west) { + edits.push(EDIT_DELETE); + i--; + current = west; + } else { + edits.push(EDIT_ADD); + j--; + current = north; + } + } + + edits.reverse(); + return edits; +} + +/** + * Splice Projection functions: + * + * A splice map is a representation of how a previous array of items + * was transformed into a new array of items. Conceptually it is a list of + * tuples of + * + * <index, removed, addedCount> + * + * which are kept in ascending index order of. The tuple represents that at + * the |index|, |removed| sequence of items were removed, and counting forward + * from |index|, |addedCount| items were added. + */ + +/** + * Lacking individual splice mutation information, the minimal set of + * splices can be synthesized given the previous state and final state of an + * array. The basic approach is to calculate the edit distance matrix and + * choose the shortest path through it. + * + * Complexity: O(l * p) + * l: The length of the current array + * p: The length of the old array + * + * @param {!Array} current The current "changed" array for which to + * calculate splices. + * @param {number} currentStart Starting index in the `current` array for + * which splices are calculated. + * @param {number} currentEnd Ending index in the `current` array for + * which splices are calculated. + * @param {!Array} old The original "unchanged" array to compare `current` + * against to determine splices. + * @param {number} oldStart Starting index in the `old` array for + * which splices are calculated. + * @param {number} oldEnd Ending index in the `old` array for + * which splices are calculated. + * @return {!Array} Returns an array of splice record objects. Each of these + * contains: `index` the location where the splice occurred; `removed` + * the array of removed items from this location; `addedCount` the number + * of items added at this location. + */ +function calcSplices(current, currentStart, currentEnd, + old, oldStart, oldEnd) { + let prefixCount = 0; + let suffixCount = 0; + let splice; + + let minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart); + if (currentStart == 0 && oldStart == 0) + prefixCount = sharedPrefix(current, old, minLength); + + if (currentEnd == current.length && oldEnd == old.length) + suffixCount = sharedSuffix(current, old, minLength - prefixCount); + + currentStart += prefixCount; + oldStart += prefixCount; + currentEnd -= suffixCount; + oldEnd -= suffixCount; + + if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0) + return []; + + if (currentStart == currentEnd) { + splice = newSplice(currentStart, [], 0); + while (oldStart < oldEnd) + splice.removed.push(old[oldStart++]); + + return [ splice ]; + } else if (oldStart == oldEnd) + return [ newSplice(currentStart, [], currentEnd - currentStart) ]; + + let ops = spliceOperationsFromEditDistances( + calcEditDistances(current, currentStart, currentEnd, + old, oldStart, oldEnd)); + + splice = undefined; + let splices = []; + let index = currentStart; + let oldIndex = oldStart; + for (let i = 0; i < ops.length; i++) { + switch(ops[i]) { + case EDIT_LEAVE: + if (splice) { + splices.push(splice); + splice = undefined; + } + + index++; + oldIndex++; + break; + case EDIT_UPDATE: + if (!splice) + splice = newSplice(index, [], 0); + + splice.addedCount++; + index++; + + splice.removed.push(old[oldIndex]); + oldIndex++; + break; + case EDIT_ADD: + if (!splice) + splice = newSplice(index, [], 0); + + splice.addedCount++; + index++; + break; + case EDIT_DELETE: + if (!splice) + splice = newSplice(index, [], 0); + + splice.removed.push(old[oldIndex]); + oldIndex++; + break; + } + } + + if (splice) { + splices.push(splice); + } + return splices; +} + +function sharedPrefix(current, old, searchLength) { + for (let i = 0; i < searchLength; i++) + if (!equals(current[i], old[i])) + return i; + return searchLength; +} + +function sharedSuffix(current, old, searchLength) { + let index1 = current.length; + let index2 = old.length; + let count = 0; + while (count < searchLength && equals(current[--index1], old[--index2])) + count++; + + return count; +} + +/** + * Returns an array of splice records indicating the minimum edits required + * to transform the `previous` array into the `current` array. + * + * Splice records are ordered by index and contain the following fields: + * - `index`: index where edit started + * - `removed`: array of removed items from this index + * - `addedCount`: number of items added at this index + * + * This function is based on the Levenshtein "minimum edit distance" + * algorithm. Note that updates are treated as removal followed by addition. + * + * The worst-case time complexity of this algorithm is `O(l * p)` + * l: The length of the current array + * p: The length of the previous array + * + * However, the worst-case complexity is reduced by an `O(n)` optimization + * to detect any shared prefix & suffix between the two arrays and only + * perform the more expensive minimum edit distance calculation over the + * non-shared portions of the arrays. + * + * @function + * @param {!Array} current The "changed" array for which splices will be + * calculated. + * @param {!Array} previous The "unchanged" original array to compare + * `current` against to determine the splices. + * @return {!Array} Returns an array of splice record objects. Each of these + * contains: `index` the location where the splice occurred; `removed` + * the array of removed items from this location; `addedCount` the number + * of items added at this location. + */ +export function calculateSplices(current, previous) { + return calcSplices(current, 0, current.length, previous, 0, + previous.length); +} + +function equals(currentValue, previousValue) { + return currentValue === previousValue; +} |