ソースを参照

Refactor: change trie object shape

SukkaW 1 年間 前
コミット
724dcdf1ad
2 ファイル変更52 行追加46 行削除
  1. 1 1
      Build/build-speedtest-domainset.ts
  2. 51 45
      Build/lib/trie.ts

+ 1 - 1
Build/build-speedtest-domainset.ts

@@ -223,7 +223,7 @@ export const buildSpeedtestDomainSet = task(import.meta.main, import.meta.path)(
       'Turkey'
       'Turkey'
     ]).reduce<Record<string, Promise<void>>>((pMap, keyword) => {
     ]).reduce<Record<string, Promise<void>>>((pMap, keyword) => {
       pMap[keyword] = span.traceChildAsync(`fetch speedtest endpoints: ${keyword}`, () => querySpeedtestApi(keyword)).then(hostnameGroup => {
       pMap[keyword] = span.traceChildAsync(`fetch speedtest endpoints: ${keyword}`, () => querySpeedtestApi(keyword)).then(hostnameGroup => {
-        hostnameGroup.forEach(hostname => {
+        return hostnameGroup.forEach(hostname => {
           if (hostname) {
           if (hostname) {
             domains.add(hostname);
             domains.add(hostname);
           }
           }

+ 51 - 45
Build/lib/trie.ts

@@ -4,23 +4,21 @@
 
 
 // const { Error, Bun, JSON, Symbol } = globalThis;
 // const { Error, Bun, JSON, Symbol } = globalThis;
 
 
-const SENTINEL = Symbol('SENTINEL');
-const PARENT = Symbol('Parent Node');
-
 const noop = () => { /** noop */ };
 const noop = () => { /** noop */ };
 
 
-type TrieNode = {
-  [SENTINEL]: boolean,
-  [PARENT]: TrieNode | null,
-  [Bun.inspect.custom]: () => string
-} & Map<string, TrieNode>;
+interface TrieNode {
+  s: boolean,
+  p: TrieNode | null,
+  [Bun.inspect.custom](): string,
+  c: Map<string, TrieNode>
+}
 
 
 const deepTrieNodeToJSON = (node: TrieNode) => {
 const deepTrieNodeToJSON = (node: TrieNode) => {
   const obj: Record<string, any> = {};
   const obj: Record<string, any> = {};
-  if (node[SENTINEL]) {
-    obj['[start]'] = node[SENTINEL];
+  if (node.s) {
+    obj['[start]'] = node.s;
   }
   }
-  node.forEach((value, key) => {
+  node.c.forEach((value, key) => {
     obj[key] = deepTrieNodeToJSON(value);
     obj[key] = deepTrieNodeToJSON(value);
   });
   });
   return obj;
   return obj;
@@ -31,11 +29,12 @@ function trieNodeInspectCustom(this: TrieNode) {
 }
 }
 
 
 const createNode = (parent: TrieNode | null = null): TrieNode => {
 const createNode = (parent: TrieNode | null = null): TrieNode => {
-  const node = new Map<string, TrieNode>() as TrieNode;
-  node[SENTINEL] = false;
-  node[PARENT] = parent;
-  node[Bun.inspect.custom] = trieNodeInspectCustom;
-  return node;
+  return {
+    s: false,
+    p: parent,
+    c: new Map<string, TrieNode>(),
+    [Bun.inspect.custom]: trieNodeInspectCustom
+  };
 };
 };
 
 
 const hostnameToTokens = (hostname: string): string[] => {
 const hostnameToTokens = (hostname: string): string[] => {
@@ -82,17 +81,17 @@ export const createTrie = (from?: string[] | Set<string> | null, hostnameMode =
     for (let i = tokens.length - 1; i >= 0; i--) {
     for (let i = tokens.length - 1; i >= 0; i--) {
       token = tokens[i];
       token = tokens[i];
 
 
-      if (node.has(token)) {
-        node = node.get(token)!;
+      if (node.c.has(token)) {
+        node = node.c.get(token)!;
 
 
         // During the adding of `[start]blog|.skk.moe` and find out that there is a `[start].skk.moe` in the trie
         // During the adding of `[start]blog|.skk.moe` and find out that there is a `[start].skk.moe` in the trie
         // Dedupe the covered subdomain by skipping
         // Dedupe the covered subdomain by skipping
-        if (smolTree && token === '.' && node[SENTINEL]) {
+        if (smolTree && token === '.' && node.s) {
           return;
           return;
         }
         }
       } else {
       } else {
         const newNode = createNode(node);
         const newNode = createNode(node);
-        node.set(token, newNode);
+        node.c.set(token, newNode);
         node = newNode;
         node = newNode;
       }
       }
     }
     }
@@ -102,33 +101,33 @@ export const createTrie = (from?: string[] | Set<string> | null, hostnameMode =
       if (tokens[0] === '.') {
       if (tokens[0] === '.') {
         // Trying to add `[start].sub.example.com` where there is already a `[start]blog.sub.example.com` in the trie
         // Trying to add `[start].sub.example.com` where there is already a `[start]blog.sub.example.com` in the trie
 
 
-        const parent = node[PARENT]!;
+        const parent = node.p!;
 
 
         // Make sure parent `[start]sub.example.com` (without dot) is removed (SETINEL to false)
         // Make sure parent `[start]sub.example.com` (without dot) is removed (SETINEL to false)
-        parent[SENTINEL] = false;
+        parent.s = false;
 
 
         // Removing the rest of the parent's child nodes
         // Removing the rest of the parent's child nodes
-        node.clear();
+        node.c.clear();
         // The SENTINEL of this node will be set to true at the end of the function, so we don't need to set it here
         // The SENTINEL of this node will be set to true at the end of the function, so we don't need to set it here
 
 
-        // we can use else-if here, because new node is empty, we don't need to check the leading "."
-      } else if (node.get('.')?.[SENTINEL] === true) {
+        // we can use else-if here, because the children is now empty, we don't need to check the leading "."
+      } else if (node.c.get('.')?.s === true) {
         // Trying to add `example.com` when there is already a `.example.com` in the trie
         // Trying to add `example.com` when there is already a `.example.com` in the trie
         // No need to increment size and set SENTINEL to true (skip this "new" item)
         // No need to increment size and set SENTINEL to true (skip this "new" item)
         return;
         return;
       }
       }
-    } else if (!node[SENTINEL]) { // smol tree don't have size, so else-if here
+    } else if (!node.s) { // smol tree don't have size, so else-if here
       size++;
       size++;
     }
     }
 
 
-    node[SENTINEL] = true;
+    node.s = true;
   };
   };
 
 
   const walkIntoLeafWithTokens = (
   const walkIntoLeafWithTokens = (
     tokens: string | string[],
     tokens: string | string[],
     onLoop: (node: TrieNode, parent: TrieNode, token: string) => void = noop
     onLoop: (node: TrieNode, parent: TrieNode, token: string) => void = noop
   ) => {
   ) => {
-    let node: TrieNode | undefined = root;
+    let node: TrieNode = root;
     let parent: TrieNode = node;
     let parent: TrieNode = node;
 
 
     let token: string;
     let token: string;
@@ -141,8 +140,12 @@ export const createTrie = (from?: string[] | Set<string> | null, hostnameMode =
       }
       }
 
 
       parent = node;
       parent = node;
-      node = node.get(token);
-      if (!node) return null;
+
+      if (node.c.has(token)) {
+        node = node.c.get(token)!;
+      } else {
+        return null;
+      }
 
 
       onLoop(node, parent, token);
       onLoop(node, parent, token);
     }
     }
@@ -170,7 +173,7 @@ export const createTrie = (from?: string[] | Set<string> | null, hostnameMode =
       node = nodeStack.pop()!;
       node = nodeStack.pop()!;
       const suffix = suffixStack.pop()!;
       const suffix = suffixStack.pop()!;
 
 
-      node.forEach((childNode, k) => {
+      node.c.forEach((childNode, k) => {
         // Pushing the child node to the stack for next iteration of DFS
         // Pushing the child node to the stack for next iteration of DFS
         nodeStack.push(childNode);
         nodeStack.push(childNode);
 
 
@@ -178,7 +181,7 @@ export const createTrie = (from?: string[] | Set<string> | null, hostnameMode =
       });
       });
 
 
       // If the node is a sentinel, we push the suffix to the results
       // If the node is a sentinel, we push the suffix to the results
-      if (node[SENTINEL]) {
+      if (node.s) {
         onMatches(suffix);
         onMatches(suffix);
       }
       }
     } while (nodeStack.length);
     } while (nodeStack.length);
@@ -200,7 +203,7 @@ export const createTrie = (from?: string[] | Set<string> | null, hostnameMode =
 
 
       // Even if the node size is 1, but the single child is ".", we should retain the branch
       // Even if the node size is 1, but the single child is ".", we should retain the branch
       // Since the "." could be special if it is the leaf-est node
       // Since the "." could be special if it is the leaf-est node
-      const onlyChild = node.size < 2 && (!hostnameMode || !node.has('.'));
+      const onlyChild = node.c.size < 2 && (!hostnameMode || !node.c.has('.'));
 
 
       if (toPrune != null) { // the top-est branch that could potentially being pruned
       if (toPrune != null) { // the top-est branch that could potentially being pruned
         if (!onlyChild) {
         if (!onlyChild) {
@@ -293,15 +296,15 @@ export const createTrie = (from?: string[] | Set<string> | null, hostnameMode =
     const res = getSingleChildLeaf(suffixToTokens(suffix));
     const res = getSingleChildLeaf(suffixToTokens(suffix));
     if (res === null) return false;
     if (res === null) return false;
 
 
-    if (!res.node[SENTINEL]) return false;
+    if (!res.node.s) return false;
 
 
     size--;
     size--;
     const { node, toPrune, tokenToPrune } = res;
     const { node, toPrune, tokenToPrune } = res;
 
 
     if (tokenToPrune && toPrune) {
     if (tokenToPrune && toPrune) {
-      toPrune.delete(tokenToPrune);
+      toPrune.c.delete(tokenToPrune);
     } else {
     } else {
-      node[SENTINEL] = false;
+      node.s = false;
     }
     }
 
 
     return true;
     return true;
@@ -315,7 +318,7 @@ export const createTrie = (from?: string[] | Set<string> | null, hostnameMode =
     const res = walkIntoLeafWithTokens(tokens);
     const res = walkIntoLeafWithTokens(tokens);
 
 
     return res
     return res
-      ? res.node[SENTINEL]
+      ? res.node.s
       : false;
       : false;
   };
   };
 
 
@@ -346,24 +349,27 @@ export const createTrie = (from?: string[] | Set<string> | null, hostnameMode =
     // Trying to whitelist `[start].sub.example.com` where there is already a `[start]blog.sub.example.com` in the trie
     // Trying to whitelist `[start].sub.example.com` where there is already a `[start]blog.sub.example.com` in the trie
     if (tokens[0] === '.') {
     if (tokens[0] === '.') {
       // If there is a `[start]sub.example.com` here, remove it
       // If there is a `[start]sub.example.com` here, remove it
-      parent[SENTINEL] = false;
+      parent.s = false;
       // Removing all the child nodes by disconnecting "."
       // Removing all the child nodes by disconnecting "."
-      parent.delete('.');
+      parent.c.delete('.');
     }
     }
 
 
     // Trying to whitelist `example.com` when there is already a `.example.com` in the trie
     // Trying to whitelist `example.com` when there is already a `.example.com` in the trie
-    const dotNode = node.get('.');
-    if (dotNode?.[SENTINEL] === true) {
-      dotNode[SENTINEL] = false;
+    const dotNode = node.c.get('.');
+    if (dotNode) {
+      dotNode.s = false;
     }
     }
+    // if (dotNode?.s === true) {
+    //   dotNode.s = false;
+    // }
 
 
     // return early if not found
     // return early if not found
-    if (!node[SENTINEL]) return;
+    if (!node.s) return;
 
 
     if (tokenToPrune && toPrune) {
     if (tokenToPrune && toPrune) {
-      toPrune.delete(tokenToPrune);
+      toPrune.c.delete(tokenToPrune);
     } else {
     } else {
-      node[SENTINEL] = false;
+      node.s = false;
     }
     }
   };
   };