UNPKG

zrender

Version:

A lightweight graphic library providing 2d draw for Apache ECharts

196 lines (174 loc) 6.34 kB
// Myers' Diff Algorithm // Modified from https://github.com/kpdecker/jsdiff/blob/master/src/diff/base.js type EqualFunc<T> = (a: T, b: T) => boolean; type DiffComponent = { count: number added: boolean removed: boolean, indices: number[] } type DiffPath = { components: DiffComponent[], newPos: number } // Using O(ND) algorithm // TODO: Optimize when diff is large. function diff<T>(oldArr: T[], newArr: T[], equals: EqualFunc<T>): DiffComponent[] { if (!equals) { equals = function (a, b) { return a === b; }; } oldArr = oldArr.slice(); newArr = newArr.slice(); // Allow subclasses to massage the input prior to running var newLen = newArr.length; var oldLen = oldArr.length; var editLength = 1; var maxEditLength = newLen + oldLen; var bestPath: DiffPath[] = [{ newPos: -1, components: [] }]; // Seed editLength = 0, i.e. the content starts with the same values var oldPos = extractCommon<T>(bestPath[0], newArr, oldArr, 0, equals); if (!oldLen // All new created || !newLen // Clear || (bestPath[0].newPos + 1 >= newLen && oldPos + 1 >= oldLen)) { var indices = []; var allCleared = !newLen && oldLen > 0; var allCreated = !oldLen && newLen > 0; for (let i = 0; i < (allCleared ? oldArr : newArr).length; i++) { indices.push(i); } // Identity per the equality and tokenizer return [{ indices: indices, count: indices.length, added: allCreated, removed: allCleared }]; } // Main worker method. checks all permutations of a given edit length for acceptance. function execEditLength() { for (let diagonalPath = -1 * editLength; diagonalPath <= editLength; diagonalPath += 2) { var basePath; var addPath = bestPath[diagonalPath - 1]; var removePath = bestPath[diagonalPath + 1]; var oldPos = (removePath ? removePath.newPos : 0) - diagonalPath; if (addPath) { // No one else is going to attempt to use this value, clear it bestPath[diagonalPath - 1] = undefined; } var canAdd = addPath && addPath.newPos + 1 < newLen; var canRemove = removePath && 0 <= oldPos && oldPos < oldLen; if (!canAdd && !canRemove) { // If this path is a terminal then prune bestPath[diagonalPath] = undefined; continue; } // Select the diagonal that we want to branch from. We select the prior // path whose position in the new string is the farthest from the origin // and does not pass the bounds of the diff graph if (!canAdd || (canRemove && addPath.newPos < removePath.newPos)) { basePath = clonePath(removePath); pushComponent(basePath.components, false, true); } else { basePath = addPath; // No need to clone, we've pulled it from the list basePath.newPos++; pushComponent(basePath.components, true, false); } oldPos = extractCommon<T>(basePath, newArr, oldArr, diagonalPath, equals); // If we have hit the end of both strings, then we are done if (basePath.newPos + 1 >= newLen && oldPos + 1 >= oldLen) { return buildValues(basePath.components); } else { // Otherwise track this path as a potential candidate and continue. bestPath[diagonalPath] = basePath; } } editLength++; } while (editLength <= maxEditLength) { var ret = execEditLength(); if (ret) { return ret; } } } function extractCommon<T>(basePath: DiffPath, newArr: T[], oldArr: T[], diagonalPath: number, equals: EqualFunc<T>) { var newLen = newArr.length; var oldLen = oldArr.length; var newPos = basePath.newPos; var oldPos = newPos - diagonalPath; var commonCount = 0; while (newPos + 1 < newLen && oldPos + 1 < oldLen && equals(newArr[newPos + 1], oldArr[oldPos + 1])) { newPos++; oldPos++; commonCount++; } if (commonCount) { basePath.components.push({ count: commonCount, added: false, removed: false, indices: [] }); } basePath.newPos = newPos; return oldPos; } function pushComponent(components: DiffComponent[], added: boolean, removed: boolean) { var last = components[components.length - 1]; if (last && last.added === added && last.removed === removed) { // We need to clone here as the component clone operation is just // as shallow array clone components[components.length - 1] = { count: last.count + 1, added, removed, indices: [] }; } else { components.push({ count: 1, added, removed, indices: [] }); } } function buildValues(components: DiffComponent[]) { var componentPos = 0; var componentLen = components.length; var newPos = 0; var oldPos = 0; for (; componentPos < componentLen; componentPos++) { var component = components[componentPos]; if (!component.removed) { var indices = []; for (let i = newPos; i < newPos + component.count; i++) { indices.push(i); } component.indices = indices; newPos += component.count; // Common case if (!component.added) { oldPos += component.count; } } else { for (let i = oldPos; i < oldPos + component.count; i++) { component.indices.push(i); } oldPos += component.count; } } return components; } function clonePath(path: DiffPath) { return { newPos: path.newPos, components: path.components.slice(0) }; } export default function arrayDiff<T>(oldArr: T[], newArr: T[], equal?: EqualFunc<T>): DiffComponent[] { return diff(oldArr, newArr, equal); }