UNPKG

@dependable/view

Version:
148 lines (134 loc) 4.69 kB
// Fork of https://github.com/derbyjs/arraydiff // // Based on some rough benchmarking, this algorithm is about O(2n) worst case, // and it can compute diffs on random arrays of length 1024 in about 34ms, // though just a few changes on an array of length 1024 takes about 0.5ms export function InsertDiff(index, values) { this._index = index; this._values = values; } export function RemoveDiff(index, howMany) { this._index = index; this._howMany = howMany; } export function MoveDiff(from, to, howMany) { this._from = from; this._to = to; this._howMany = howMany; } /** @internal */ export function arrayDiff(before, after, equalFn) { // Find all items in both the before and after array, and represent them // as moves. Many of these "moves" may end up being discarded in the last // pass if they are from an index to the same index, but we don't know this // up front, since we haven't yet offset the indices. // // Also keep a map of all the indices accounted for in the before and after // arrays. These maps are used next to create insert and remove diffs. const beforeLength = before.length; const afterLength = after.length; const moves = []; const beforeMarked = {}; const afterMarked = {}; for (let beforeIndex = 0; beforeIndex < beforeLength; beforeIndex++) { const beforeItem = before[beforeIndex]; for (let afterIndex = 0; afterIndex < afterLength; afterIndex++) { if (afterMarked[afterIndex]) continue; if (!equalFn(beforeItem, after[afterIndex])) continue; const from = beforeIndex; const to = afterIndex; let howMany = 0; do { beforeMarked[beforeIndex++] = afterMarked[afterIndex++] = true; howMany++; } while ( beforeIndex < beforeLength && afterIndex < afterLength && equalFn(before[beforeIndex], after[afterIndex]) && !afterMarked[afterIndex] ); moves.push(new MoveDiff(from, to, howMany)); beforeIndex--; break; } } // Create a remove for all of the items in the before array that were // not marked as being matched in the after array as well const removes = []; for (let beforeIndex = 0; beforeIndex < beforeLength; ) { if (beforeMarked[beforeIndex]) { beforeIndex++; continue; } const index = beforeIndex; let howMany = 0; while (beforeIndex < beforeLength && !beforeMarked[beforeIndex++]) { howMany++; } removes.push(new RemoveDiff(index, howMany)); } // Create an insert for all of the items in the after array that were // not marked as being matched in the before array as well const inserts = []; for (let afterIndex = 0; afterIndex < afterLength; ) { if (afterMarked[afterIndex]) { afterIndex++; continue; } const index = afterIndex; let howMany = 0; while (afterIndex < afterLength && !afterMarked[afterIndex++]) { howMany++; } const values = after.slice(index, index + howMany); inserts.push(new InsertDiff(index, values)); } const insertsLength = inserts.length; const removesLength = removes.length; const movesLength = moves.length; let i, j; // Offset subsequent removes and moves by removes let count = 0; for (i = 0; i < removesLength; i++) { const remove = removes[i]; remove._index -= count; count += remove._howMany; for (j = 0; j < movesLength; j++) { const move = moves[j]; if (move._from >= remove._index) move._from -= remove._howMany; } } // Offset moves by inserts for (i = insertsLength; i--; ) { const insert = inserts[i]; const howMany = insert._values.length; for (j = movesLength; j--; ) { const move = moves[j]; if (move._to >= insert._index) move._to -= howMany; } } // Offset the to of moves by later moves for (i = movesLength; i-- > 1; ) { const move = moves[i]; if (move._to === move._from) continue; for (j = i; j--; ) { const earlier = moves[j]; if (earlier._to >= move._to) earlier._to -= move._howMany; if (earlier._to >= move._from) earlier._to += move._howMany; } } // Only output moves that end up having an effect after offsetting const outputMoves = []; // Offset the from of moves by earlier moves for (i = 0; i < movesLength; i++) { const move = moves[i]; if (move._to === move._from) continue; outputMoves.push(move); for (j = i + 1; j < movesLength; j++) { const later = moves[j]; if (later._from >= move._from) later._from -= move._howMany; if (later._from >= move._to) later._from += move._howMany; } } return removes.concat(outputMoves, inserts); }