2019-02-15 11:11:55 -05:00
|
|
|
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
|
|
|
interface FarthestPoint {
|
|
|
|
y: number;
|
|
|
|
id: number;
|
|
|
|
}
|
|
|
|
|
2019-03-05 14:58:28 -05:00
|
|
|
export enum DiffType {
|
|
|
|
removed = "removed",
|
|
|
|
common = "common",
|
|
|
|
added = "added"
|
|
|
|
}
|
2019-02-15 11:11:55 -05:00
|
|
|
|
|
|
|
export interface DiffResult<T> {
|
|
|
|
type: DiffType;
|
|
|
|
value: T;
|
|
|
|
}
|
|
|
|
|
|
|
|
const REMOVED = 1;
|
|
|
|
const COMMON = 2;
|
|
|
|
const ADDED = 3;
|
|
|
|
|
2019-03-04 19:53:35 -05:00
|
|
|
function createCommon<T>(A: T[], B: T[], reverse?: boolean): T[] {
|
2019-02-15 11:11:55 -05:00
|
|
|
const common = [];
|
|
|
|
if (A.length === 0 || B.length === 0) return [];
|
|
|
|
for (let i = 0; i < Math.min(A.length, B.length); i += 1) {
|
|
|
|
if (
|
|
|
|
A[reverse ? A.length - i - 1 : i] === B[reverse ? B.length - i - 1 : i]
|
|
|
|
) {
|
|
|
|
common.push(A[reverse ? A.length - i - 1 : i]);
|
|
|
|
} else {
|
|
|
|
return common;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return common;
|
|
|
|
}
|
|
|
|
|
2019-03-04 19:53:35 -05:00
|
|
|
export default function diff<T>(A: T[], B: T[]): Array<DiffResult<T>> {
|
|
|
|
const prefixCommon = createCommon(A, B);
|
|
|
|
const suffixCommon = createCommon(
|
|
|
|
A.slice(prefixCommon.length),
|
|
|
|
B.slice(prefixCommon.length),
|
|
|
|
true
|
|
|
|
).reverse();
|
|
|
|
A = suffixCommon.length
|
|
|
|
? A.slice(prefixCommon.length, -suffixCommon.length)
|
|
|
|
: A.slice(prefixCommon.length);
|
|
|
|
B = suffixCommon.length
|
|
|
|
? B.slice(prefixCommon.length, -suffixCommon.length)
|
|
|
|
: B.slice(prefixCommon.length);
|
|
|
|
const swapped = B.length > A.length;
|
|
|
|
[A, B] = swapped ? [B, A] : [A, B];
|
|
|
|
const M = A.length;
|
|
|
|
const N = B.length;
|
|
|
|
if (!M && !N && !suffixCommon.length && !prefixCommon.length) return [];
|
|
|
|
if (!N) {
|
|
|
|
return [
|
2019-04-24 07:41:23 -04:00
|
|
|
...prefixCommon.map(
|
|
|
|
(c): DiffResult<typeof c> => ({ type: DiffType.common, value: c })
|
|
|
|
),
|
|
|
|
...A.map(
|
|
|
|
(a): DiffResult<typeof a> => ({
|
|
|
|
type: swapped ? DiffType.added : DiffType.removed,
|
|
|
|
value: a
|
|
|
|
})
|
|
|
|
),
|
|
|
|
...suffixCommon.map(
|
|
|
|
(c): DiffResult<typeof c> => ({ type: DiffType.common, value: c })
|
|
|
|
)
|
2019-03-04 19:53:35 -05:00
|
|
|
];
|
|
|
|
}
|
|
|
|
const offset = N;
|
|
|
|
const delta = M - N;
|
|
|
|
const size = M + N + 1;
|
|
|
|
const fp = new Array(size).fill({ y: -1 });
|
2019-06-19 00:22:01 -04:00
|
|
|
/**
|
|
|
|
* INFO:
|
|
|
|
* This buffer is used to save memory and improve performance.
|
|
|
|
* The first half is used to save route and last half is used to save diff
|
|
|
|
* type.
|
|
|
|
* This is because, when I kept new uint8array area to save type,performance
|
|
|
|
* worsened.
|
|
|
|
*/
|
2019-03-04 19:53:35 -05:00
|
|
|
const routes = new Uint32Array((M * N + size + 1) * 2);
|
|
|
|
const diffTypesPtrOffset = routes.length / 2;
|
|
|
|
let ptr = 0;
|
|
|
|
let p = -1;
|
|
|
|
|
2019-02-15 11:11:55 -05:00
|
|
|
function backTrace<T>(
|
|
|
|
A: T[],
|
|
|
|
B: T[],
|
|
|
|
current: FarthestPoint,
|
|
|
|
swapped: boolean
|
2019-03-04 19:53:35 -05:00
|
|
|
): Array<{
|
|
|
|
type: DiffType;
|
|
|
|
value: T;
|
|
|
|
}> {
|
2019-02-15 11:11:55 -05:00
|
|
|
const M = A.length;
|
|
|
|
const N = B.length;
|
|
|
|
const result = [];
|
|
|
|
let a = M - 1;
|
|
|
|
let b = N - 1;
|
|
|
|
let j = routes[current.id];
|
|
|
|
let type = routes[current.id + diffTypesPtrOffset];
|
|
|
|
while (true) {
|
|
|
|
if (!j && !type) break;
|
|
|
|
const prev = j;
|
|
|
|
if (type === REMOVED) {
|
|
|
|
result.unshift({
|
2019-03-05 14:58:28 -05:00
|
|
|
type: swapped ? DiffType.removed : DiffType.added,
|
2019-02-15 11:11:55 -05:00
|
|
|
value: B[b]
|
|
|
|
});
|
|
|
|
b -= 1;
|
|
|
|
} else if (type === ADDED) {
|
|
|
|
result.unshift({
|
2019-03-05 14:58:28 -05:00
|
|
|
type: swapped ? DiffType.added : DiffType.removed,
|
2019-02-15 11:11:55 -05:00
|
|
|
value: A[a]
|
|
|
|
});
|
|
|
|
a -= 1;
|
|
|
|
} else {
|
2019-03-05 14:58:28 -05:00
|
|
|
result.unshift({ type: DiffType.common, value: A[a] });
|
2019-02-15 11:11:55 -05:00
|
|
|
a -= 1;
|
|
|
|
b -= 1;
|
|
|
|
}
|
|
|
|
j = routes[prev];
|
|
|
|
type = routes[prev + diffTypesPtrOffset];
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
function createFP(
|
|
|
|
slide: FarthestPoint,
|
|
|
|
down: FarthestPoint,
|
|
|
|
k: number,
|
2019-03-04 19:53:35 -05:00
|
|
|
M: number
|
2019-02-15 11:11:55 -05:00
|
|
|
): FarthestPoint {
|
2019-11-13 13:42:34 -05:00
|
|
|
if (slide && slide.y === -1 && down && down.y === -1)
|
2019-02-15 11:11:55 -05:00
|
|
|
return { y: 0, id: 0 };
|
|
|
|
if (
|
|
|
|
(down && down.y === -1) ||
|
|
|
|
k === M ||
|
|
|
|
(slide && slide.y) > (down && down.y) + 1
|
|
|
|
) {
|
|
|
|
const prev = slide.id;
|
|
|
|
ptr++;
|
|
|
|
routes[ptr] = prev;
|
|
|
|
routes[ptr + diffTypesPtrOffset] = ADDED;
|
|
|
|
return { y: slide.y, id: ptr };
|
|
|
|
} else {
|
|
|
|
const prev = down.id;
|
|
|
|
ptr++;
|
|
|
|
routes[ptr] = prev;
|
|
|
|
routes[ptr + diffTypesPtrOffset] = REMOVED;
|
|
|
|
return { y: down.y + 1, id: ptr };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function snake<T>(
|
|
|
|
k: number,
|
|
|
|
slide: FarthestPoint,
|
|
|
|
down: FarthestPoint,
|
2019-03-04 19:53:35 -05:00
|
|
|
_offset: number,
|
2019-02-15 11:11:55 -05:00
|
|
|
A: T[],
|
|
|
|
B: T[]
|
2019-03-04 19:53:35 -05:00
|
|
|
): FarthestPoint {
|
2019-02-15 11:11:55 -05:00
|
|
|
const M = A.length;
|
|
|
|
const N = B.length;
|
2019-03-04 19:53:35 -05:00
|
|
|
if (k < -N || M < k) return { y: -1, id: -1 };
|
|
|
|
const fp = createFP(slide, down, k, M);
|
2019-02-15 11:11:55 -05:00
|
|
|
while (fp.y + k < M && fp.y < N && A[fp.y + k] === B[fp.y]) {
|
|
|
|
const prev = fp.id;
|
|
|
|
ptr++;
|
|
|
|
fp.id = ptr;
|
|
|
|
fp.y += 1;
|
|
|
|
routes[ptr] = prev;
|
|
|
|
routes[ptr + diffTypesPtrOffset] = COMMON;
|
|
|
|
}
|
|
|
|
return fp;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (fp[delta + offset].y < N) {
|
|
|
|
p = p + 1;
|
|
|
|
for (let k = -p; k < delta; ++k) {
|
|
|
|
fp[k + offset] = snake(
|
|
|
|
k,
|
|
|
|
fp[k - 1 + offset],
|
|
|
|
fp[k + 1 + offset],
|
|
|
|
offset,
|
|
|
|
A,
|
|
|
|
B
|
|
|
|
);
|
|
|
|
}
|
|
|
|
for (let k = delta + p; k > delta; --k) {
|
|
|
|
fp[k + offset] = snake(
|
|
|
|
k,
|
|
|
|
fp[k - 1 + offset],
|
|
|
|
fp[k + 1 + offset],
|
|
|
|
offset,
|
|
|
|
A,
|
|
|
|
B
|
|
|
|
);
|
|
|
|
}
|
|
|
|
fp[delta + offset] = snake(
|
|
|
|
delta,
|
|
|
|
fp[delta - 1 + offset],
|
|
|
|
fp[delta + 1 + offset],
|
|
|
|
offset,
|
|
|
|
A,
|
|
|
|
B
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return [
|
2019-04-24 07:41:23 -04:00
|
|
|
...prefixCommon.map(
|
|
|
|
(c): DiffResult<typeof c> => ({ type: DiffType.common, value: c })
|
|
|
|
),
|
2019-02-15 11:11:55 -05:00
|
|
|
...backTrace(A, B, fp[delta + offset], swapped),
|
2019-04-24 07:41:23 -04:00
|
|
|
...suffixCommon.map(
|
|
|
|
(c): DiffResult<typeof c> => ({ type: DiffType.common, value: c })
|
|
|
|
)
|
2019-02-15 11:11:55 -05:00
|
|
|
];
|
|
|
|
}
|