@seriousme/opifex
Version:
MQTT client & server for Deno & NodeJS
107 lines (95 loc) • 2.91 kB
text/typescript
type Parts = string[];
export class Trie<T> {
#value: Array<T>;
#children: Map<string, Trie<T>>;
private separator = "/";
private wildcardOne = "+";
private wildcardSubtree = "#";
private reservedPrefix = "$";
private looseCompare: boolean;
constructor(looseCompare = false) {
this.#value = [];
this.#children = new Map();
this.looseCompare = looseCompare;
}
matchChild(child: string, parts: Parts): Array<T> {
const childNode = this.#children.get(child);
return childNode ? childNode._match(parts) : [];
}
match(key: string) {
const parts = key.split(this.separator);
// toplevel wildcards are not allowed to match toplevel reserved topics
if (parts.length > 0 && parts[0].charAt(0) === this.reservedPrefix) {
return this._matchPrefix(parts);
}
return this._match(parts);
}
private _matchPrefix(parts: Parts): Array<T> {
if (parts.length === 0) {
return this.#value ? this.#value : [];
}
const [first, ...rest] = parts;
return this.matchChild(first, rest);
}
private _match(parts: Parts): Array<T> {
if (parts.length === 0) {
return this.#value ? this.#value : [];
}
const [first, ...rest] = parts;
const exact = this.matchChild(first, rest);
const single = this.matchChild(this.wildcardOne, rest);
const subtree = this.matchChild(this.wildcardSubtree, []);
const results = exact.concat(single, subtree);
return results;
}
add(key: string, value: T) {
return this._add(key.split(this.separator), value);
}
private _add(parts: Parts, value: T) {
if (parts.length === 0) {
this.#value = this.#value.concat(value);
return;
}
const [first, ...rest] = parts;
const child = this.#children.get(first);
if (child instanceof Trie) {
child._add(rest, value);
} else {
const node = new Trie<T>(this.looseCompare);
this.#children.set(first, node);
node._add(rest, value);
}
return;
}
remove(key: string, value: T) {
return this._remove(key.split(this.separator), value);
}
private _remove(parts: Parts, value: T): void {
if (parts.length === 0) {
const arr = this.#value || [];
this.#value = arr.filter(this.filter(value));
return;
}
const [first, ...rest] = parts;
const node = this.#children.get(first);
if (node) {
node._remove(rest, value);
if (node.#value.length === 0 && this.#children.size === 0) {
this.#children.delete(first);
}
}
}
private filter(value: T): (value: T, index: number, array: T[]) => boolean {
if (this.looseCompare && typeof value === "object") {
return (item) => {
for (const key in value) {
if (value[key] !== item[key]) {
return true;
}
}
return false;
};
}
return (item) => item !== value;
}
}