summaryrefslogtreecommitdiff
path: root/std/testing/diff.ts
diff options
context:
space:
mode:
authorRyan Dahl <ry@tinyclouds.org>2019-10-09 17:10:09 -0400
committerRyan Dahl <ry@tinyclouds.org>2019-10-09 17:10:09 -0400
commit151ce0266eb4de2c8fc600c81c192a5f791b6169 (patch)
tree7cb04016a1c7ee88adde83814548d7a9409dcde3 /std/testing/diff.ts
parenta355f7c807686918734416d91b79c26c21effba9 (diff)
Move everything into std subdir
Diffstat (limited to 'std/testing/diff.ts')
-rw-r--r--std/testing/diff.ts220
1 files changed, 220 insertions, 0 deletions
diff --git a/std/testing/diff.ts b/std/testing/diff.ts
new file mode 100644
index 000000000..dd544ac24
--- /dev/null
+++ b/std/testing/diff.ts
@@ -0,0 +1,220 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+interface FarthestPoint {
+ y: number;
+ id: number;
+}
+
+export enum DiffType {
+ removed = "removed",
+ common = "common",
+ added = "added"
+}
+
+export interface DiffResult<T> {
+ type: DiffType;
+ value: T;
+}
+
+const REMOVED = 1;
+const COMMON = 2;
+const ADDED = 3;
+
+function createCommon<T>(A: T[], B: T[], reverse?: boolean): T[] {
+ 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;
+}
+
+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 [
+ ...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 })
+ )
+ ];
+ }
+ const offset = N;
+ const delta = M - N;
+ const size = M + N + 1;
+ const fp = new Array(size).fill({ y: -1 });
+ /**
+ * 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.
+ */
+ const routes = new Uint32Array((M * N + size + 1) * 2);
+ const diffTypesPtrOffset = routes.length / 2;
+ let ptr = 0;
+ let p = -1;
+
+ function backTrace<T>(
+ A: T[],
+ B: T[],
+ current: FarthestPoint,
+ swapped: boolean
+ ): Array<{
+ type: DiffType;
+ value: T;
+ }> {
+ 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({
+ type: swapped ? DiffType.removed : DiffType.added,
+ value: B[b]
+ });
+ b -= 1;
+ } else if (type === ADDED) {
+ result.unshift({
+ type: swapped ? DiffType.added : DiffType.removed,
+ value: A[a]
+ });
+ a -= 1;
+ } else {
+ result.unshift({ type: DiffType.common, value: A[a] });
+ a -= 1;
+ b -= 1;
+ }
+ j = routes[prev];
+ type = routes[prev + diffTypesPtrOffset];
+ }
+ return result;
+ }
+
+ function createFP(
+ slide: FarthestPoint,
+ down: FarthestPoint,
+ k: number,
+ M: number
+ ): FarthestPoint {
+ if (slide && slide.y === -1 && (down && down.y === -1))
+ 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,
+ _offset: number,
+ A: T[],
+ B: T[]
+ ): FarthestPoint {
+ const M = A.length;
+ const N = B.length;
+ if (k < -N || M < k) return { y: -1, id: -1 };
+ const fp = createFP(slide, down, k, M);
+ 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 [
+ ...prefixCommon.map(
+ (c): DiffResult<typeof c> => ({ type: DiffType.common, value: c })
+ ),
+ ...backTrace(A, B, fp[delta + offset], swapped),
+ ...suffixCommon.map(
+ (c): DiffResult<typeof c> => ({ type: DiffType.common, value: c })
+ )
+ ];
+}