From 2fae22b4eb13d3d3c2e173d2d662d258948e19d6 Mon Sep 17 00:00:00 2001 From: takker99 <37929109+takker99@users.noreply.github.com> Date: Thu, 1 Dec 2022 16:50:08 +0900 Subject: [PATCH 1/2] =?UTF-8?q?:fire:=20=E3=81=84=E3=82=89=E3=81=AA?= =?UTF-8?q?=E3=81=84=E3=83=A9=E3=82=A4=E3=82=BB=E3=83=B3=E3=82=B9=E3=83=95?= =?UTF-8?q?=E3=82=A1=E3=82=A4=E3=83=AB=E3=82=92=E6=B6=88=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- browser/websocket/LICENSE | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 browser/websocket/LICENSE diff --git a/browser/websocket/LICENSE b/browser/websocket/LICENSE deleted file mode 100644 index 736c542..0000000 --- a/browser/websocket/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2021 takker - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. From 8a3e379a0a0d762e73ade0858c1f4493c87e42eb Mon Sep 17 00:00:00 2001 From: takker99 <37929109+takker99@users.noreply.github.com> Date: Thu, 1 Dec 2022 16:55:21 +0900 Subject: [PATCH 2/2] =?UTF-8?q?:recycle:=20onp=20algorithm=E3=82=92?= =?UTF-8?q?=E4=BB=96=E3=81=AErepo=E3=81=AB=E5=88=87=E3=82=8A=E5=87=BA?= =?UTF-8?q?=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- browser/websocket/diff.test.ts | 184 ----------------------------- browser/websocket/diff.ts | 181 ---------------------------- browser/websocket/diffToChanges.ts | 2 +- deps/onp.ts | 1 + 4 files changed, 2 insertions(+), 366 deletions(-) delete mode 100644 browser/websocket/diff.test.ts delete mode 100644 browser/websocket/diff.ts create mode 100644 deps/onp.ts diff --git a/browser/websocket/diff.test.ts b/browser/websocket/diff.test.ts deleted file mode 100644 index f572a38..0000000 --- a/browser/websocket/diff.test.ts +++ /dev/null @@ -1,184 +0,0 @@ -/// -import { Change, diff, ExtendedChange, toExtendedChanges } from "./diff.ts"; -import { assertEquals, assertStrictEquals } from "../../deps/testing.ts"; - -Deno.test("diff()", async (t) => { - await t.step("check variables", async ({ step }) => { - await step("return arguments", () => { - assertEquals(diff("aaa", "bbbb").from, "aaa"); - assertEquals(diff("aaa", "bbbb").to, "bbbb"); - const left = ["aaa", "bbb", 111] as const; - const right = ["ccc", "ddd", 222] as const; - assertStrictEquals(diff(left, right).from, left); - assertStrictEquals(diff(left, right).to, right); - }); - }); - await t.step("string", async ({ step }) => { - const diffData: [string, string, Change[]][] = [ - ["kitten", "sitting", [ - { value: "s", type: "added" }, - { value: "k", type: "deleted" }, - { value: "i", type: "common" }, - { value: "t", type: "common" }, - { value: "t", type: "common" }, - { value: "i", type: "added" }, - { value: "e", type: "deleted" }, - { value: "n", type: "common" }, - { value: "g", type: "added" }, - ]], - ["sitting", "kitten", [ - { value: "s", type: "deleted" }, - { value: "k", type: "added" }, - { value: "i", type: "common" }, - { value: "t", type: "common" }, - { value: "t", type: "common" }, - { value: "i", type: "deleted" }, - { value: "e", type: "added" }, - { value: "n", type: "common" }, - { value: "g", type: "deleted" }, - ]], - ]; - for (const [before, after, changes] of diffData) { - await step( - `${before}->${after}`, - () => assertEquals([...diff(before, after).buildSES()], changes), - ); - } - }); -}); - -Deno.test("toExtendedChanges()", async (t) => { - await t.step("only", async ({ step }) => { - await step("only added", () => { - const before: Change[] = [ - { value: "aaa", type: "added" }, - { value: "bbb", type: "added" }, - { value: "ccc", type: "added" }, - ]; - const after: ExtendedChange[] = [ - { value: "aaa", type: "added" }, - { value: "bbb", type: "added" }, - { value: "ccc", type: "added" }, - ]; - assertEquals([...toExtendedChanges(before)], after); - }); - await step("only deleted", () => { - const before: Change[] = [ - { value: "aaa", type: "deleted" }, - { value: "bbb", type: "deleted" }, - { value: "ccc", type: "deleted" }, - ]; - const after: ExtendedChange[] = [ - { value: "aaa", type: "deleted" }, - { value: "bbb", type: "deleted" }, - { value: "ccc", type: "deleted" }, - ]; - assertEquals([...toExtendedChanges(before)], after); - }); - await step("only common", () => { - const before: Change[] = [ - { value: "aaa", type: "common" }, - { value: "bbb", type: "common" }, - { value: "ccc", type: "common" }, - ]; - const after: ExtendedChange[] = [ - { value: "aaa", type: "common" }, - { value: "bbb", type: "common" }, - { value: "ccc", type: "common" }, - ]; - assertEquals([...toExtendedChanges(before)], after); - }); - }); - - await t.step("mixed", async ({ step }) => { - await step("added and deleted", () => { - const before: Change[] = [ - { value: "111", type: "added" }, - { value: "aaa", type: "added" }, - { value: "bbb", type: "deleted" }, - { value: "222", type: "added" }, - { value: "eee", type: "added" }, - { value: "fff", type: "deleted" }, - { value: "ggg", type: "deleted" }, - { value: "222", type: "added" }, - { value: "eee", type: "added" }, - { value: "ggg", type: "deleted" }, - { value: "222", type: "added" }, - { value: "fff", type: "deleted" }, - { value: "ggg", type: "deleted" }, - { value: "222", type: "added" }, - { value: "eee", type: "added" }, - ]; - const after: ExtendedChange[] = [ - { value: "111", oldValue: "bbb", type: "replaced" }, - { value: "aaa", oldValue: "fff", type: "replaced" }, - { value: "222", oldValue: "ggg", type: "replaced" }, - { value: "eee", oldValue: "ggg", type: "replaced" }, - { value: "222", oldValue: "fff", type: "replaced" }, - { value: "eee", oldValue: "ggg", type: "replaced" }, - { value: "222", type: "added" }, - { value: "222", type: "added" }, - { value: "eee", type: "added" }, - ]; - assertEquals([...toExtendedChanges(before)], after); - }); - await step("added and deleted and common", () => { - const before: Change[] = [ - { value: "111", type: "added" }, - { value: "aaa", type: "added" }, - { value: "bbb", type: "deleted" }, - { value: "ccc", type: "common" }, - { value: "ddd", type: "common" }, - { value: "222", type: "added" }, - { value: "eee", type: "added" }, - { value: "fff", type: "deleted" }, - { value: "ggg", type: "deleted" }, - { value: "ddd", type: "common" }, - { value: "222", type: "added" }, - { value: "eee", type: "added" }, - { value: "ggg", type: "deleted" }, - { value: "ddd", type: "common" }, - { value: "222", type: "added" }, - { value: "fff", type: "deleted" }, - { value: "ggg", type: "deleted" }, - { value: "ddd", type: "common" }, - { value: "222", type: "added" }, - { value: "eee", type: "added" }, - { value: "ddd", type: "common" }, - { value: "fff", type: "deleted" }, - { value: "ggg", type: "deleted" }, - { value: "ddd", type: "common" }, - { value: "222", type: "added" }, - { value: "eee", type: "added" }, - { value: "fff", type: "deleted" }, - { value: "ggg", type: "deleted" }, - { value: "222", type: "added" }, - ]; - const after: ExtendedChange[] = [ - { value: "111", oldValue: "bbb", type: "replaced" }, - { value: "aaa", type: "added" }, - { value: "ccc", type: "common" }, - { value: "ddd", type: "common" }, - { value: "222", oldValue: "fff", type: "replaced" }, - { value: "eee", oldValue: "ggg", type: "replaced" }, - { value: "ddd", type: "common" }, - { value: "222", oldValue: "ggg", type: "replaced" }, - { value: "eee", type: "added" }, - { value: "ddd", type: "common" }, - { value: "222", oldValue: "fff", type: "replaced" }, - { value: "ggg", type: "deleted" }, - { value: "ddd", type: "common" }, - { value: "222", type: "added" }, - { value: "eee", type: "added" }, - { value: "ddd", type: "common" }, - { value: "fff", type: "deleted" }, - { value: "ggg", type: "deleted" }, - { value: "ddd", type: "common" }, - { value: "222", oldValue: "fff", type: "replaced" }, - { value: "eee", oldValue: "ggg", type: "replaced" }, - { value: "222", type: "added" }, - ]; - assertEquals([...toExtendedChanges(before)], after); - }); - }); -}); diff --git a/browser/websocket/diff.ts b/browser/websocket/diff.ts deleted file mode 100644 index a68780a..0000000 --- a/browser/websocket/diff.ts +++ /dev/null @@ -1,181 +0,0 @@ -/** - * 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 Added { - value: T; - type: "added"; -} -export interface Deleted { - value: T; - type: "deleted"; -} -export interface Common { - value: T; - type: "common"; -} -export type Change = Added | Deleted | Common; -export interface Replaced { - value: T; - oldValue: T; - type: "replaced"; -} -export type ExtendedChange = Change | Replaced; - -export interface DiffResult { - from: ArrayLike; - to: ArrayLike; - editDistance: number; - buildSES(): Generator, void, unknown>; -} - -export const diff = ( - left: ArrayLike, - right: ArrayLike, -): DiffResult => { - 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; - const path = new Array(MAXSIZE); - path.fill(-1); - const pathpos = [] as [Position, number][]; - - function snake(k: number, p: number, pp: number) { - let y = Math.max(p, pp); - let x = y - k; - - 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]]); - return y; - } - - const fp = new Array(MAXSIZE); - fp.fill(-1); - let p = -1; - const delta = b.length - a.length; - do { - ++p; - 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], - ); - } while (fp[delta + offset] !== b.length); - - const epc = [] as Position[]; - let r = path[delta + offset]; - while (r !== -1) { - epc.push(pathpos[r][0]); - r = pathpos[r][1]; - } - - return { - from: left, - to: right, - editDistance: delta + p * 2, - 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 ? "deleted" : "added" }; - ++yIndex; - } else if (y - x < yIndex - xIndex) { - yield { value: a[xIndex], type: reversed ? "added" : "deleted" }; - ++xIndex; - } else { - yield { value: a[xIndex], type: "common" }; - ++xIndex; - ++yIndex; - } - } - } - }, - }; -}; - -export function* toExtendedChanges( - changes: Iterable>, -): Generator, void, unknown> { - let addedList = [] as Added[]; - let deletedList = [] as Deleted[]; - - function* flush() { - if (addedList.length > deletedList.length) { - for (let i = 0; i < deletedList.length; i++) { - yield makeReplaced( - addedList[i], - deletedList[i], - ); - } - for (let i = deletedList.length; i < addedList.length; i++) { - yield addedList[i]; - } - } else { - for (let i = 0; i < addedList.length; i++) { - yield makeReplaced( - addedList[i], - deletedList[i], - ); - } - for (let i = addedList.length; i < deletedList.length; i++) { - yield deletedList[i]; - } - } - addedList = []; - deletedList = []; - } - - for (const change of changes) { - switch (change.type) { - case "added": - addedList.push(change); - break; - case "deleted": - deletedList.push(change); - break; - case "common": - yield* flush(); - yield change; - break; - } - } - yield* flush(); -} - -const makeReplaced = ( - left: Added, - right: Deleted, -): Replaced => ({ - value: left.value, - oldValue: right.value, - type: "replaced", -}); - -function* reverse(list: ArrayLike) { - for (let i = list.length - 1; i >= 0; i--) { - yield list[i]; - } -} diff --git a/browser/websocket/diffToChanges.ts b/browser/websocket/diffToChanges.ts index 84f7f38..b4c02f3 100644 --- a/browser/websocket/diffToChanges.ts +++ b/browser/websocket/diffToChanges.ts @@ -1,4 +1,4 @@ -import { diff, toExtendedChanges } from "./diff.ts"; +import { diff, toExtendedChanges } from "../../deps/onp.ts"; import type { Line } from "../../deps/scrapbox.ts"; import type { DeleteCommit, diff --git a/deps/onp.ts b/deps/onp.ts new file mode 100644 index 0000000..fe489c5 --- /dev/null +++ b/deps/onp.ts @@ -0,0 +1 @@ +export * from "https://raw.githubusercontent.com/takker99/onp/0.0.1/mod.ts";