fp
に該当するmod.ts/**
* The algorithm implemented here is based on "An O(NP) Sequence Comparison Algorithm"
* by described by Sun Wu, Udi Manber and Gene Myers */
/** LICENSE: https://github.com/cubicdaiya/onp/blob/master/COPYING */
type Position = {
x: number;
y: number;
};
export interface Change<T> {
value: T;
type: "add" | "delete" | "common";
}
export interface DiffResult<T> {
from: ArrayLike<T>;
to: ArrayLike<T>;
editDistance: number;
buildSES(): Generator<Change<T>, void, unknown>;
}
export const diff = <T>(
left: ArrayLike<T>,
right: ArrayLike<T>,
): DiffResult<T> => {
const reversed = left.length > right.length;
const a = reversed ? right : left;
const b = reversed ? left : right;
const offset = a.length + 1;
const MAXSIZE = a.length + b.length + 3;
path
はkから snake()
で計算した座標が入っている pathpos
の要素番号を取得する函数に相当するもの pathpos
の要素数が500とか600に膨れ上がるmod.ts const path = new Array<number>(MAXSIZE);
path.fill(-1);
const pathpos = [] as [Position, number][];
const snake = (k: number, p: number, pp: number) => {
let y = Math.max(p, pp);
let x = y - k;
mod.ts while (x < a.length && y < b.length && a[x] === b[y]) {
++x;
++y;
}
path[k + offset] = pathpos.length;
pathpos.push([{ x, y }, path[k + (p > pp ? -1 : +1) + offset]]);
mod.ts return y;
};
mod.ts const fp = new Array<number>(MAXSIZE);
fp.fill(-1);
let p = -1;
const delta = b.length - a.length;
do {
++p;
offset
を足して正にしているmod.ts for (let k = -p; k <= delta - 1; ++k) {
fp[k + offset] = snake(k, fp[k - 1 + offset] + 1, fp[k + 1 + offset]);
}
for (let k = delta + p; k >= delta + 1; --k) {
fp[k + offset] = snake(k, fp[k - 1 + offset] + 1, fp[k + 1 + offset]);
}
fp[delta + offset] = snake(delta, fp[delta - 1 + offset] + 1, fp[delta + 1 + offset]);
mod.ts } while (fp[delta + offset] !== b.length);
mod.ts const epc = [] as Position[];
{
let r = path[delta + offset];
while (r !== -1) {
epc.push(pathpos[r][0]);
r = pathpos[r][1];
}
}
mod.ts console.log({ path, pathpos, epc });
return {
from: left,
to: right,
editDistance: delta + p * 2,
mod.ts buildSES: function* () {
let xIndex = 0;
let yIndex = 0;
for (const { x, y } of reverse(epc)) {
while (xIndex < x || yIndex < y) {
if (y - x > yIndex - xIndex) {
yield { value: b[yIndex], type: reversed ? "delete" : "add" };
++yIndex;
} else if (y - x < yIndex - xIndex) {
yield { value: a[xIndex], type: reversed ? "add" : "delete" };
++xIndex;
} else {
yield { value: a[xIndex], type: "common" };
++xIndex;
++yIndex;
}
}
}
},
};
};
mod.tsfunction* reverse<T>(list: ArrayLike<T>): Generator<T, void, unknown> {
for (let i = list.length - 1; i >= 0; i--) {
yield list[i];
}
}
cli.tsimport { Command } from "https://deno.land/x/cliffy@v0.19.2/command/mod.ts";
import { diff } from "./mod.ts";
const { args, options } = await new Command()
.name("diff")
.version("0.1.0")
.description(
"O(NP) algorithmによる文字列差分表示のテストプログラム",
)
.arguments("<left> <right>")
.option("--vertical", "差分を縦に表示する")
.parse(Deno.args);
const left = String(args[0]);
const right = String(args[1]);
const { buildSES, from, to } = diff(left, right);
console.log(`${from} → ${to}`);
if (options?.vertical) {
console.log(
[...buildSES()].map(({ value, type }) =>
type === "add"
? `+ ${value}`
: type === "delete"
? `- ${value}`
: ` ${value}`
).join("\n"),
);
} else {
let signs = "";
let str = "";
for (const {value, type} of buildSES()) {
str += value;
const half = value
switch (type) {
case "add":
signs += "+";
break;
case "delete":
signs += "-";
break;
default:
signs += " ";
break;
}
}
console.log(`${signs}\n${str}`);
}
shdeno run "https://scrapbox.io/api/code/takker/O(NP)_algorithm/cli.ts" sitting kitten
log{
path: [
-1, -1, -1, -1, -1, 6,
7, 8, 11, 10, 9, -1,
-1, -1, -1, -1
],
pathpos: [
[ { x: 0, y: 0 }, -1 ],
[ { x: 0, y: 1 }, 0 ],
[ { x: 1, y: 0 }, 0 ],
[ { x: 4, y: 4 }, 1 ],
[ { x: 0, y: 2 }, 1 ],
[ { x: 4, y: 5 }, 3 ],
[ { x: 2, y: 0 }, 2 ],
[ { x: 5, y: 4 }, 3 ],
[ { x: 6, y: 6 }, 5 ],
[ { x: 0, y: 3 }, 4 ],
[ { x: 4, y: 6 }, 5 ],
[ { x: 6, y: 7 }, 8 ]
],
epc: [
{ x: 6, y: 7 },
{ x: 6, y: 6 },
{ x: 4, y: 5 },
{ x: 4, y: 4 },
{ x: 0, y: 1 },
{ x: 0, y: 0 }
]
}
kitten → sitting
+ s
- k
i
t
t
+ i
- e
n
+ g