417 lines
11 KiB
TypeScript
417 lines
11 KiB
TypeScript
export type GenericIndexable<T> = { [key: number]: T; readonly length: number };
|
|
type TypedArray =
|
|
| Int8Array
|
|
| Int16Array
|
|
| Int32Array
|
|
| Uint8Array
|
|
| Uint16Array
|
|
| Uint32Array
|
|
| Float32Array
|
|
| Float64Array;
|
|
export type Indexable<T> = string | T[] | TypedArray | GenericIndexable<T>;
|
|
|
|
export interface Sliceable<T> extends GenericIndexable<T> {
|
|
slice(start: number, end?: number): this;
|
|
}
|
|
|
|
type Vec4 = [number, number, number, number];
|
|
type Vec3 = [number, number, number];
|
|
|
|
type Comparator = (i: number, j: number) => boolean;
|
|
|
|
type DiffState = {
|
|
i: number;
|
|
N: number;
|
|
j: number;
|
|
M: number;
|
|
Z: number;
|
|
b: TypedArray;
|
|
eq: (x: number, y: number) => boolean;
|
|
should_break?: () => boolean;
|
|
pxs: number;
|
|
pxe: number;
|
|
pys: number;
|
|
pye: number;
|
|
oxs: number;
|
|
oxe: number;
|
|
oys: number;
|
|
oye: number;
|
|
stack_top: number;
|
|
stack_base: number[];
|
|
};
|
|
|
|
// Find the list of differences between 2 lists by
|
|
// recursive subdivision, requring O(min(N,M)) space
|
|
// and O(min(N,M)*D) worst-case execution time where
|
|
// D is the number of differences.
|
|
function diff_internal(state: DiffState, c: number): number {
|
|
const { b, eq, stack_base } = state;
|
|
let { i, N, j, M, Z, stack_top } = state;
|
|
for (;;) {
|
|
switch (c) {
|
|
case 0: {
|
|
Z_block: while (N > 0 && M > 0) {
|
|
b.fill(0, 0, 2 * Z);
|
|
|
|
const W = N - M;
|
|
const L = N + M;
|
|
const parity = L & 1;
|
|
const offsetx = i + N - 1;
|
|
const offsety = j + M - 1;
|
|
const hmax = (L + parity) / 2;
|
|
let z: number;
|
|
h_loop: for (let h = 0; h <= hmax; h++) {
|
|
if (state.should_break && state.should_break()) {
|
|
return 0;
|
|
}
|
|
const kmin = 2 * Math.max(0, h - M) - h;
|
|
const kmax = h - 2 * Math.max(0, h - N);
|
|
|
|
// Forward pass
|
|
for (let k = kmin; k <= kmax; k += 2) {
|
|
const gkm = b[k - 1 - Z * Math.floor((k - 1) / Z)];
|
|
const gkp = b[k + 1 - Z * Math.floor((k + 1) / Z)];
|
|
const u = k === -h || (k !== h && gkm < gkp) ? gkp : gkm + 1;
|
|
const v = u - k;
|
|
let x = u;
|
|
let y = v;
|
|
while (x < N && y < M && eq(i + x, j + y)) x++, y++;
|
|
b[k - Z * Math.floor(k / Z)] = x;
|
|
if (
|
|
parity === 1 &&
|
|
(z = W - k) >= 1 - h &&
|
|
z < h &&
|
|
x + b[Z + z - Z * Math.floor(z / Z)] >= N
|
|
) {
|
|
if (h > 1 || x !== u) {
|
|
stack_base[stack_top++] = i + x;
|
|
stack_base[stack_top++] = N - x;
|
|
stack_base[stack_top++] = j + y;
|
|
stack_base[stack_top++] = M - y;
|
|
N = u;
|
|
M = v;
|
|
Z = 2 * (Math.min(N, M) + 1);
|
|
continue Z_block;
|
|
} else break h_loop;
|
|
}
|
|
}
|
|
|
|
// Reverse pass
|
|
for (let k = kmin; k <= kmax; k += 2) {
|
|
const pkm = b[Z + k - 1 - Z * Math.floor((k - 1) / Z)];
|
|
const pkp = b[Z + k + 1 - Z * Math.floor((k + 1) / Z)];
|
|
const u = k === -h || (k !== h && pkm < pkp) ? pkp : pkm + 1;
|
|
const v = u - k;
|
|
let x = u;
|
|
let y = v;
|
|
while (x < N && y < M && eq(offsetx - x, offsety - y)) x++, y++;
|
|
b[Z + k - Z * Math.floor(k / Z)] = x;
|
|
if (
|
|
parity === 0 &&
|
|
(z = W - k) >= -h &&
|
|
z <= h &&
|
|
x + b[z - Z * Math.floor(z / Z)] >= N
|
|
) {
|
|
if (h > 0 || x !== u) {
|
|
stack_base[stack_top++] = i + N - u;
|
|
stack_base[stack_top++] = u;
|
|
stack_base[stack_top++] = j + M - v;
|
|
stack_base[stack_top++] = v;
|
|
N = N - x;
|
|
M = M - y;
|
|
Z = 2 * (Math.min(N, M) + 1);
|
|
continue Z_block;
|
|
} else break h_loop;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (N === M) continue;
|
|
if (M > N) {
|
|
i += N;
|
|
j += N;
|
|
M -= N;
|
|
N = 0;
|
|
} else {
|
|
i += M;
|
|
j += M;
|
|
N -= M;
|
|
M = 0;
|
|
}
|
|
|
|
// We already know either N or M is zero, so we can
|
|
// skip the extra check at the top of the loop.
|
|
break;
|
|
}
|
|
|
|
// yield delete_start, delete_end, insert_start, insert_end
|
|
// At this point, at least one of N & M is zero, or we
|
|
// wouldn't have gotten out of the preceding loop yet.
|
|
if (N + M !== 0) {
|
|
if (state.pxe === i || state.pye === j) {
|
|
// it is a contiguous difference extend the existing one
|
|
state.pxe = i + N;
|
|
state.pye = j + M;
|
|
} else {
|
|
const sx = state.pxs;
|
|
state.oxs = state.pxs;
|
|
state.oxe = state.pxe;
|
|
state.oys = state.pys;
|
|
state.oye = state.pye;
|
|
|
|
// Defer this one until we can check the next one
|
|
state.pxs = i;
|
|
state.pxe = i + N;
|
|
state.pys = j;
|
|
state.pye = j + M;
|
|
|
|
if (sx >= 0) {
|
|
state.i = i;
|
|
state.N = N;
|
|
state.j = j;
|
|
state.M = M;
|
|
state.Z = Z;
|
|
state.stack_top = stack_top;
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
case 1: {
|
|
if (stack_top === 0) return 2;
|
|
|
|
M = stack_base[--stack_top];
|
|
j = stack_base[--stack_top];
|
|
N = stack_base[--stack_top];
|
|
i = stack_base[--stack_top];
|
|
Z = 2 * (Math.min(N, M) + 1);
|
|
c = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class DiffGen implements IterableIterator<Vec4> {
|
|
private c = 0;
|
|
private result: IteratorResult<Vec4, undefined> = {
|
|
value: null as any,
|
|
done: false,
|
|
};
|
|
|
|
constructor(private state: DiffState) {}
|
|
|
|
[Symbol.iterator]() {
|
|
return this;
|
|
}
|
|
|
|
next() {
|
|
const { state, result } = this;
|
|
|
|
if (state.should_break && state.should_break()) {
|
|
result.done = true;
|
|
result.value = undefined;
|
|
return result;
|
|
}
|
|
|
|
if (this.c > 1) {
|
|
result.done = true;
|
|
result.value = undefined;
|
|
return result;
|
|
}
|
|
const c = diff_internal(state, this.c);
|
|
if (state.should_break && state.should_break()) {
|
|
result.done = true;
|
|
result.value = undefined;
|
|
return result;
|
|
}
|
|
|
|
this.c = c;
|
|
if (c === 1) {
|
|
result.value = [state.oxs, state.oxe, state.oys, state.oye];
|
|
return result;
|
|
}
|
|
if (state.pxs >= 0) {
|
|
result.value = [state.pxs, state.pxe, state.pys, state.pye];
|
|
return result;
|
|
}
|
|
result.done = true;
|
|
result.value = undefined;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
export function diff_core(
|
|
i: number,
|
|
N: number,
|
|
j: number,
|
|
M: number,
|
|
eq: Comparator,
|
|
should_break?: () => boolean
|
|
): IterableIterator<Vec4> {
|
|
const Z = (Math.min(N, M) + 1) * 2;
|
|
const L = N + M;
|
|
const b = new (L < 256 ? Uint8Array : L < 65536 ? Uint16Array : Uint32Array)(
|
|
2 * Z
|
|
);
|
|
|
|
return new DiffGen({
|
|
i,
|
|
N,
|
|
j,
|
|
M,
|
|
Z,
|
|
b,
|
|
eq,
|
|
should_break,
|
|
pxs: -1,
|
|
pxe: -1,
|
|
pys: -1,
|
|
pye: -1,
|
|
oxs: -1,
|
|
oxe: -1,
|
|
oys: -1,
|
|
oye: -1,
|
|
stack_top: 0,
|
|
stack_base: [],
|
|
});
|
|
}
|
|
|
|
export function diff<T extends Indexable<unknown>>(
|
|
xs: T,
|
|
ys: T,
|
|
eq?: Comparator,
|
|
should_break?: () => boolean
|
|
): IterableIterator<Vec4> {
|
|
let [i, N, M] = [0, xs.length, ys.length];
|
|
|
|
if (typeof eq === "function") {
|
|
// eliminate common prefix
|
|
while (i < N && i < M && eq(i, i)) i++;
|
|
|
|
// check for equality
|
|
if (i === N && i === M) return [][Symbol.iterator]();
|
|
|
|
// eliminate common suffix
|
|
while (eq(--N, --M) && N > i && M > i);
|
|
} else {
|
|
// eliminate common prefix
|
|
while (i < N && i < M && xs[i] === ys[i]) i++;
|
|
|
|
// check for equality
|
|
if (i === N && i === M) return [][Symbol.iterator]();
|
|
|
|
// eliminate common suffix
|
|
while (xs[--N] === ys[--M] && N > i && M > i);
|
|
|
|
eq = (i, j) => xs[i] === ys[j];
|
|
}
|
|
|
|
return diff_core(i, N + 1 - i, i, M + 1 - i, eq, should_break);
|
|
}
|
|
|
|
class LCSGen implements IterableIterator<Vec3> {
|
|
private i = 0;
|
|
private j = 0;
|
|
|
|
constructor(
|
|
private diff: IterableIterator<Vec4>,
|
|
private N: number
|
|
) {}
|
|
|
|
[Symbol.iterator]() {
|
|
return this;
|
|
}
|
|
|
|
next() {
|
|
// Convert diffs into the dual similar-aligned representation.
|
|
// In each iteration, i and j will be aligned at the beginning
|
|
// of a shared section. This section is yielded, and i and j
|
|
// are re-aligned at the end of the succeeding unique sections.
|
|
const rec = this.diff.next();
|
|
if (rec.done) {
|
|
const { i, j, N } = this;
|
|
if (i < N) {
|
|
rec.done = false as any;
|
|
rec.value = [i, j, N - i] as any;
|
|
this.i = N;
|
|
}
|
|
return rec as IteratorResult<Vec3>;
|
|
}
|
|
const v = rec.value;
|
|
const sx = v[0];
|
|
const ex = v[1];
|
|
const ey = v[3];
|
|
const { i, j } = this;
|
|
if (i !== sx) {
|
|
v.length--; // re-use the vec4 as a vec3 to avoid allocation
|
|
v[0] = i;
|
|
v[1] = j;
|
|
v[2] = sx - i;
|
|
}
|
|
|
|
this.i = ex;
|
|
this.j = ey;
|
|
|
|
return rec as unknown as IteratorResult<Vec3>;
|
|
}
|
|
}
|
|
|
|
export function lcs<T extends Indexable<unknown>>(
|
|
xs: T,
|
|
ys: T,
|
|
eq?: Comparator
|
|
): IterableIterator<Vec3> {
|
|
return new LCSGen(diff(xs, ys, eq), xs.length);
|
|
}
|
|
|
|
export function* calcPatch<T, S extends Sliceable<T>>(
|
|
xs: S,
|
|
ys: S,
|
|
eq?: Comparator,
|
|
should_break?: () => boolean
|
|
): Generator<[number, number, S]> {
|
|
// Taking subarrays is cheaper than slicing for TypedArrays.
|
|
const slice = ArrayBuffer.isView(xs)
|
|
? (Uint8Array.prototype.subarray as unknown as typeof xs.slice)
|
|
: xs.slice;
|
|
|
|
for (const v of diff(xs, ys, eq, should_break)) {
|
|
v[2] = slice.call(ys, v[2], v[3]) as any;
|
|
yield v as any;
|
|
}
|
|
}
|
|
|
|
export function* applyPatch<T, S extends Sliceable<T>>(
|
|
xs: S,
|
|
patch: Iterable<[number, number, S]>
|
|
): Generator<S> {
|
|
let i = 0; // Taking subarrays is cheaper than slicing for TypedArrays.
|
|
const slice = ArrayBuffer.isView(xs)
|
|
? (Uint8Array.prototype.subarray as unknown as typeof xs.slice)
|
|
: xs.slice;
|
|
for (const [dels, dele, ins] of patch) {
|
|
if (i < dels) yield slice.call(xs, i, dels);
|
|
if (ins.length > 0) yield ins;
|
|
i = dele;
|
|
}
|
|
if (i < xs.length) yield slice.call(xs, i);
|
|
}
|
|
|
|
export function* calcSlices<T, S extends Sliceable<T>>(
|
|
xs: S,
|
|
ys: S,
|
|
eq?: Comparator
|
|
): Generator<[-1 | 0 | 1, S]> {
|
|
let i = 0; // Taking subarrays is cheaper than slicing for TypedArrays.
|
|
const slice = ArrayBuffer.isView(xs)
|
|
? (Uint8Array.prototype.subarray as unknown as typeof xs.slice)
|
|
: xs.slice;
|
|
for (const [dels, dele, inss, inse] of diff(xs, ys, eq)) {
|
|
if (i < dels) yield [0, slice.call(xs, i, dels)];
|
|
if (dels < dele) yield [-1, slice.call(xs, dels, dele)];
|
|
if (inss < inse) yield [1, slice.call(ys, inss, inse)];
|
|
i = dele;
|
|
}
|
|
if (i < xs.length) yield [0, xs.slice(i)];
|
|
}
|