Files
redux-scraper/app/javascript/server/buildUsersTrie.js
2023-08-18 19:22:39 -07:00

141 lines
4.1 KiB
JavaScript

function buildUsersTrie(users) {
const rootNode = new trie();
users.forEach(([id, name]) => {
rootNode.insert(name.toLowerCase(), [id, name]);
});
return JSON.stringify(rootNode.serialize());
}
class trie_node {
constructor() {
this.terminal = false;
this.children = new Map();
}
serialize() {
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 {
constructor() {
this.root = new trie_node();
this.elements = 0;
}
serialize() {
return this.root.serialize();
}
get length() {
return this.elements;
}
get(key) {
const node = this.getNode(key);
if (node) {
return node.value;
}
return null;
}
contains(key) {
const node = this.getNode(key);
return !!node;
}
insert(key, value) {
let node = this.root;
let remaining = key;
while (remaining.length > 0) {
let child = 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();
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();
node.children.set(remaining, child);
remaining = "";
}
node = child;
}
if (!node.terminal) {
node.terminal = true;
this.elements += 1;
}
node.value = value;
}
remove(key) {
const node = this.getNode(key);
if (node) {
node.terminal = false;
this.elements -= 1;
}
}
map(prefix, func) {
const mapped = [];
const node = this.getNode(prefix);
const stack = [];
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;
}
getNode(key) {
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;
}
commonPrefix(a, b) {
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);
}
}