@sanity/diff
Version:
Generates diffs between documents and primitive types
78 lines (69 loc) • 2.12 kB
text/typescript
/*
* Longest common subsequence implementation, for diffing arrays
* Reference: http://en.wikipedia.org/wiki/Longest_common_subsequence_problem
*/
type NumberArray = number[]
type LengthMatrix = NumberArray[]
type Subsequence<E> = {
sequence: E[]
prevIndices: number[]
nextIndices: number[]
}
export function getLongestCommonSubsequence<E>(previous: E[], next: E[]): Subsequence<E> {
const matrix = getLengthMatrix(previous, next)
const result = backtrack(matrix, previous, next)
return result
}
function getLengthMatrix<E>(previous: E[], next: E[]): LengthMatrix {
const len1 = previous.length
const len2 = next.length
let x = 0
let y = 0
// initialize empty matrix of len1+1 x len2+1
const matrix: LengthMatrix = new Array(len1 + 1)
for (x = 0; x < len1 + 1; x++) {
matrix[x] = [len2 + 1]
for (y = 0; y < len2 + 1; y++) {
matrix[x][y] = 0
}
}
// save sequence lengths for each coordinate
for (x = 1; x < len1 + 1; x++) {
for (y = 1; y < len2 + 1; y++) {
if (previous[x - 1] === next[y - 1]) {
matrix[x][y] = matrix[x - 1][y - 1] + 1
} else {
matrix[x][y] = Math.max(matrix[x - 1][y], matrix[x][y - 1])
}
}
}
return matrix
}
function backtrack<E>(matrix: LengthMatrix, previous: E[], next: E[]): Subsequence<E> {
let prevIndex = previous.length
let nextIndex = next.length
const subsequence: Subsequence<E> = {
sequence: [],
prevIndices: [],
nextIndices: [],
}
while (prevIndex !== 0 && nextIndex !== 0) {
const areEqual = previous[prevIndex - 1] === next[nextIndex - 1]
if (areEqual) {
subsequence.sequence.unshift(previous[prevIndex - 1])
subsequence.prevIndices.unshift(prevIndex - 1)
subsequence.nextIndices.unshift(nextIndex - 1)
--prevIndex
--nextIndex
} else {
const valueAtMatrixAbove = matrix[prevIndex][nextIndex - 1]
const valueAtMatrixLeft = matrix[prevIndex - 1][nextIndex]
if (valueAtMatrixAbove > valueAtMatrixLeft) {
--nextIndex
} else {
--prevIndex
}
}
}
return subsequence
}