Skip to content

Commit

Permalink
Merge pull request #2 from se2p/refactoring
Browse files Browse the repository at this point in the history
Refactoring
  • Loading branch information
schweikl authored Nov 10, 2024
2 parents 83d07e6 + 22351d4 commit 668170e
Show file tree
Hide file tree
Showing 7 changed files with 33 additions and 39 deletions.
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": "schweikl@fim.uni-passau.de"
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

0 comments on commit 668170e

Please sign in to comment.