164 lines
4.0 KiB
TypeScript
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);
|
|
}
|
|
}
|