Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring #2

Merged
merged 4 commits into from
Nov 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# pq-distance: Approximate Tree-Edit Distance for Node.js

[![npm version](https://badge.fury.io/js/@se2p%2Fpq-distance.svg)](https://www.npmjs.com/package/@se2p/pq-distance)
![CI status](https://github.com/se2p/pq-distance/actions/workflows/ci.yml/badge.svg?branch=main)

Modern TypeScript implementation of pq-gram distance, an efficient approximation for tree-edit distance. Algorithm based
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"lint": "eslint src",
"test": "jest"
},
"keywords": [],
"keywords": ["tree-edit distance", "approximation"],
"author": {
"name": "Sebastian Schweikl",
"email": "[email protected]"
Expand Down
4 changes: 2 additions & 2 deletions src/PQGramProfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,12 @@ export class PQGramProfile {
requirePositiveInteger(p, q);

const profile = new PQGramProfile(p + q);
const workQueue: [T, Register][] = [[tree.root, new Register(p)]];
const workQueue: [T, Register][] = [[tree.root, Register.ofLength(p)]];

while (workQueue.length > 0) {
const [r, _anc] = workQueue.shift()!;
const anc = _anc.shift(tree.getLabel(r));
let sib = new Register(q);
let sib = Register.ofLength(q);

const children = tree.getChildren(r);

Expand Down
25 changes: 9 additions & 16 deletions src/Register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ const NIL = Symbol("*");
*/
type Label = string | typeof NIL;

class _Register {
/**
* A fixed-length shift register of labels.
*/
export class Register {

/**
* Current labels stored by the register.
Expand All @@ -23,11 +26,7 @@ class _Register {
* @param contents The labels with which the register is initially filled.
* @protected
*/
protected constructor(contents: Label[]) {
if (contents.length === 0) {
throw new Error("empty contents");
}

private constructor(contents: Label[]) {
this._contents = contents;
}

Expand All @@ -48,7 +47,7 @@ class _Register {
const contents = [...this._contents];
contents.push(label);
contents.shift();
return new _Register(contents);
return new Register(contents);
}

/**
Expand All @@ -57,7 +56,7 @@ class _Register {
* @param that The other register to concatenate with.
*/
concat(that: Register): Register {
return new _Register([...this._contents, ...that._contents]);
return new Register([...this._contents, ...that._contents]);
}

/**
Expand All @@ -66,19 +65,13 @@ class _Register {
toJSON(): (string | null)[] {
return this._contents.map((l) => l === NIL ? null : l);
}
}

/**
* A fixed-length shift register of labels.
*/
export class Register extends _Register {

/**
* Constructs a new, initially empty shift register which can hold the given fixed number of labels.
* @param n The fixed length of the register.
*/
constructor(n: number) {
static ofLength(n: number) {
requirePositiveInteger(n);
super(Array<Label>(n).fill(NIL));
return new Register(Array<Label>(n).fill(NIL));
}
}
12 changes: 6 additions & 6 deletions test/PQGramProfile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ describe("A pq-gram profile", () => {
it("should increase its length by 1", () => {
const registerLength = 1;
const p = new PQGramProfile(registerLength);
p.add(new Register(registerLength));
p.add(Register.ofLength(registerLength));
expect(p).toHaveLength(1);
});

it("should increase its length even if it already contains the same register", () => {
const registerLength = 1;
const p = new PQGramProfile(registerLength);
const times = 5;
const r = new Register(registerLength);
const r = Register.ofLength(registerLength);
for (let i = 0; i < times; i++) {
p.add(r);
}
Expand All @@ -40,7 +40,7 @@ describe("A pq-gram profile", () => {

it("should throw if the register's length is incompatible", () => {
const p = new PQGramProfile(1);
const r = new Register(2);
const r = Register.ofLength(2);
expect(() => p.add(r)).toThrow();
});
});
Expand All @@ -50,7 +50,7 @@ describe("A pq-gram profile", () => {
const registerLength = 1;
const p = new PQGramProfile(registerLength);
for (let i = 0; i < 5; i++) {
p.add(new Register(1));
p.add(Register.ofLength(1));
}
const q = new PQGramProfile(registerLength);
expect(p.intersect(q)).toBe(0);
Expand All @@ -61,12 +61,12 @@ describe("A pq-gram profile", () => {
const registerLength = 1;
const p = new PQGramProfile(registerLength);
for (let i = 0; i < 5; i++) {
p.add(new Register(registerLength));
p.add(Register.ofLength(registerLength));
}

const q = new PQGramProfile(registerLength);
for (let i = 0; i < 3; i++) {
q.add(new Register(registerLength));
q.add(Register.ofLength(registerLength));
}

const before = {
Expand Down
26 changes: 13 additions & 13 deletions test/Register.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@ import {newRegister} from "./util";
describe("A register", () => {
describe("that is newly constructed", () => {
it.each([0, -1, 1.1234])("should throw an error for invalid size %d", (i) => {
expect(() => new Register(i)).toThrow();
expect(() => Register.ofLength(i)).toThrow();
});

it("should have the specified size", () => {
const length = 42;
expect(new Register(42)).toHaveLength(length);
expect(Register.ofLength(42)).toHaveLength(length);
});

it("should be empty", () => {
const length = 5;
const r = new Register(length);
const r = Register.ofLength(length);
const actual = r.toJSON();
const expected = Array(length).fill(null);
expect(actual).toStrictEqual(expected);
Expand All @@ -23,29 +23,29 @@ describe("A register", () => {

describe("that is serialized", () => {
it("should return a fresh object", () => {
const r = new Register(5);
const r = Register.ofLength(5);
const j = r.toJSON();
const k = r.toJSON();
expect(j).not.toBe(k);
});

it("should result in an array with the labels", () => {
const r = new Register(3).shift("foo").shift("bar");
const r = Register.ofLength(3).shift("foo").shift("bar");
expect(r.toJSON()).toStrictEqual([null, "foo", "bar"]);
});
});

describe("that is shifted", () => {
it("should not modify the callee object", () => {
const r = new Register(5);
const r = Register.ofLength(5);
const before = r.toJSON();
r.shift("foo");
const after = r.toJSON();
expect(before).toStrictEqual(after);
});

it("should result in a new register with the given label appended", () => {
const r = new Register(2);
it("should result in a new Register with the given label appended", () => {
const r = Register.ofLength(2);
const t = r.shift("foo");
expect(r).not.toBe(t);
expect(t.toJSON()).toStrictEqual([null, "foo"]);
Expand All @@ -63,17 +63,17 @@ describe("A register", () => {
});

describe("that is concatenated with another register", () => {
it("should return a new register", () => {
const r = new Register(1);
const s = new Register(2);
it("should return a new Register", () => {
const r = Register.ofLength(1);
const s = Register.ofLength(2);
const t = r.concat(s);
expect(t).not.toBe(r);
expect(t).not.toBe(s);
});

it("should return a register of the correct length", () => {
const r = new Register(1);
const s = new Register(2);
const r = Register.ofLength(1);
const s = Register.ofLength(2);
const t = r.concat(s);
expect(t).toHaveLength(r.length + s.length);
});
Expand Down
2 changes: 1 addition & 1 deletion test/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {PQGramProfile, PQTree} from "../src/PQGramProfile";
export function newRegister(r: Register | number, ...contents: (string | null)[]): Register {
return contents.reduce(
(r, l) => l === null ? r.shift() : r.shift(l),
typeof r === "number" ? new Register(r) : r);
typeof r === "number" ? Register.ofLength(r) : r);
}

export function newPQGramProfile(registerLength: number, contents: (string | null)[][]): PQGramProfile {
Expand Down