Files
redux-scraper/app/javascript/server/buildUsersTrie.ts
Dylan Knutson 29fc1d3bc6 clientside trie
2023-04-04 17:13:40 -07:00

164 lines
4.0 KiB
TypeScript

type UserRow = [number, string];
function buildUsersTrie(users: UserRow[]): string {
const rootNode = new trie<[number, string]>();
users.forEach(([id, name]) => {
rootNode.insert(name.toLowerCase(), [id, name]);
});
return JSON.stringify(rootNode.serialize());
}
class trie_node<T> {
public terminal: boolean;
public value: T;
public children: Map<string, trie_node<T>>;
constructor() {
this.terminal = false;
this.children = new Map();
}
public serialize(): Object {
const { terminal, value, children } = this;
let mapped = {};
let numChildren = 0;
Object.keys(Object.fromEntries(children)).forEach((childKey) => {
numChildren += 1;
mapped[childKey] = children.get(childKey).serialize();
});
return {
t: this.terminal ? 1 : 0,
v: value,
c: numChildren > 0 ? mapped : undefined,
};
}
}
class trie<T> {
public root: trie_node<T>;
public elements: number;
constructor() {
this.root = new trie_node<T>();
this.elements = 0;
}
public serialize(): Object {
return this.root.serialize();
}
public get length(): number {
return this.elements;
}
public get(key: string): T | null {
const node = this.getNode(key);
if (node) {
return node.value;
}
return null;
}
public contains(key: string): boolean {
const node = this.getNode(key);
return !!node;
}
public insert(key: string, value: T): void {
let node = this.root;
let remaining = key;
while (remaining.length > 0) {
let child: trie_node<T> = null;
for (const childKey of node.children.keys()) {
const prefix = this.commonPrefix(remaining, childKey);
if (!prefix.length) {
continue;
}
if (prefix.length === childKey.length) {
// enter child node
child = node.children.get(childKey);
remaining = remaining.slice(childKey.length);
break;
} else {
// split the child
child = new trie_node<T>();
child.children.set(
childKey.slice(prefix.length),
node.children.get(childKey)
);
node.children.delete(childKey);
node.children.set(prefix, child);
remaining = remaining.slice(prefix.length);
break;
}
}
if (!child && remaining.length) {
child = new trie_node<T>();
node.children.set(remaining, child);
remaining = "";
}
node = child;
}
if (!node.terminal) {
node.terminal = true;
this.elements += 1;
}
node.value = value;
}
public remove(key: string): void {
const node = this.getNode(key);
if (node) {
node.terminal = false;
this.elements -= 1;
}
}
public map<U>(prefix: string, func: (key: string, value: T) => U): U[] {
const mapped = [];
const node = this.getNode(prefix);
const stack: [string, trie_node<T>][] = [];
if (node) {
stack.push([prefix, node]);
}
while (stack.length) {
const [key, node] = stack.pop();
if (node.terminal) {
mapped.push(func(key, node.value));
}
for (const c of node.children.keys()) {
stack.push([key + c, node.children.get(c)]);
}
}
return mapped;
}
private getNode(key: string): trie_node<T> | null {
let node = this.root;
let remaining = key;
while (node && remaining.length > 0) {
let child = null;
for (let i = 1; i <= remaining.length; i += 1) {
child = node.children.get(remaining.slice(0, i));
if (child) {
remaining = remaining.slice(i);
break;
}
}
node = child;
}
return remaining.length === 0 && node && node.terminal ? node : null;
}
private commonPrefix(a: string, b: string): string {
const shortest = Math.min(a.length, b.length);
let i = 0;
for (; i < shortest; i += 1) {
if (a[i] !== b[i]) {
break;
}
}
return a.slice(0, i);
}
}