From 2bbc122b8553b6fa1f84590a8bf2b50e1a25e325 Mon Sep 17 00:00:00 2001 From: SukkaW Date: Tue, 15 Oct 2024 18:35:13 +0800 Subject: [PATCH] Replace sortDomains w/ trie dump sort --- Build/lib/misc.ts | 4 +- Build/lib/rules/base.ts | 8 --- Build/lib/rules/domainset.ts | 21 ++++--- Build/lib/rules/ruleset.ts | 7 +-- Build/lib/trie.ts | 105 ++++++++++++++++++++++++++--------- 5 files changed, 99 insertions(+), 46 deletions(-) diff --git a/Build/lib/misc.ts b/Build/lib/misc.ts index 151fef16..9cb13ccb 100644 --- a/Build/lib/misc.ts +++ b/Build/lib/misc.ts @@ -103,9 +103,9 @@ export function withBannerArray(title: string, description: string[] | readonly ]; }; -export function mergeHeaders(headersA: T | undefined, headersB: T): T { +export function mergeHeaders(headersA: T | undefined, headersB: T | undefined): T { if (headersA == null) { - return headersB; + return headersB!; } if (Array.isArray(headersB)) { diff --git a/Build/lib/rules/base.ts b/Build/lib/rules/base.ts index 0d331325..3dac5b98 100644 --- a/Build/lib/rules/base.ts +++ b/Build/lib/rules/base.ts @@ -88,14 +88,6 @@ export abstract class RuleOutput { return this; } - protected apexDomainMap: Map | null = null; - protected subDomainMap: Map | null = null; - withDomainMap(apexDomainMap: Map, subDomainMap: Map) { - this.apexDomainMap = apexDomainMap; - this.subDomainMap = subDomainMap; - return this; - } - addDomain(domain: string) { this.domainTrie.add(domain); return this; diff --git a/Build/lib/rules/domainset.ts b/Build/lib/rules/domainset.ts index 9370c431..872575e7 100644 --- a/Build/lib/rules/domainset.ts +++ b/Build/lib/rules/domainset.ts @@ -20,13 +20,8 @@ export class DomainsetOutput extends RuleOutput { return; } results.push(domain); - }); + }, true); - if (!this.apexDomainMap || !this.subDomainMap) { - const { domainMap, subdomainMap } = buildParseDomainMap(results); - this.apexDomainMap = domainMap; - this.subDomainMap = subdomainMap; - } const sorted = results; sorted.push('this_ruleset_is_made_by_sukkaw.ruleset.skk.moe'); @@ -63,9 +58,21 @@ export class DomainsetOutput extends RuleOutput { } satisfies SingboxSourceFormat); } + protected apexDomainMap: Map | null = null; + protected subDomainMap: Map | null = null; + withDomainMap(apexDomainMap: Map, subDomainMap: Map) { + this.apexDomainMap = apexDomainMap; + this.subDomainMap = subDomainMap; + return this; + } + getStatMap() { invariant(this.$preprocessed, 'Non dumped yet'); - invariant(this.apexDomainMap, 'Missing apex domain map'); + + if (!this.apexDomainMap || !this.subDomainMap) { + const { domainMap } = buildParseDomainMap(this.$preprocessed); + this.apexDomainMap = domainMap; + } return Array.from(this.$preprocessed .reduce>( diff --git a/Build/lib/rules/ruleset.ts b/Build/lib/rules/ruleset.ts index 648eab96..38cc1c8b 100644 --- a/Build/lib/rules/ruleset.ts +++ b/Build/lib/rules/ruleset.ts @@ -4,7 +4,6 @@ import createKeywordFilter from '../aho-corasick'; import { appendArrayInPlace } from '../append-array-in-place'; import { appendArrayFromSet } from '../misc'; import type { SingboxSourceFormat } from '../singbox'; -import { sortDomains } from '../stable-sort-domain'; import { RuleOutput } from './base'; import picocolors from 'picocolors'; import { normalizeDomain } from '../normalize-domain'; @@ -24,9 +23,9 @@ export class RulesetOutput extends RuleOutput { const domainSuffixes: string[] = []; const sortedDomainRules: string[] = []; - for (const domain of sortDomains(this.domainTrie.dump(), this.apexDomainMap, this.subDomainMap)) { + this.domainTrie.dump((domain) => { if (kwfilter(domain)) { - continue; + return; } if (domain[0] === '.') { domainSuffixes.push(domain.slice(1)); @@ -35,7 +34,7 @@ export class RulesetOutput extends RuleOutput { domains.push(domain); sortedDomainRules.push(`DOMAIN,${domain}`); } - } + }, true); return [domains, domainSuffixes, sortedDomainRules] satisfies Preprocessed; } diff --git a/Build/lib/trie.ts b/Build/lib/trie.ts index 62aa9bf9..7b596910 100644 --- a/Build/lib/trie.ts +++ b/Build/lib/trie.ts @@ -177,33 +177,76 @@ abstract class Triebase { return true; }; + private static bfsResults: [node: TrieNode | null, suffix: string[]] = [null, []]; + + private static bfs(this: void, nodeStack: FIFO>, suffixStack: FIFO) { + const node = nodeStack.dequeue()!; + const suffix = suffixStack.dequeue()!; + + node[3].forEach((childNode, k) => { + // Pushing the child node to the stack for next iteration of DFS + nodeStack.enqueue(childNode); + + suffixStack.enqueue([k, ...suffix]); + }); + + Triebase.bfsResults[0] = node; + Triebase.bfsResults[1] = suffix; + + return Triebase.bfsResults; + } + + private static bfsWithSort(this: void, nodeStack: FIFO>, suffixStack: FIFO) { + const node = nodeStack.dequeue()!; + const suffix = suffixStack.dequeue()!; + + if (node[3].size) { + const keys = Array.from(node[3].keys()).sort(Triebase.compare); + + for (let i = 0, l = keys.length; i < l; i++) { + const key = keys[i]; + const childNode = node[3].get(key)!; + + // Pushing the child node to the stack for next iteration of DFS + nodeStack.enqueue(childNode); + suffixStack.enqueue([key, ...suffix]); + } + } + + Triebase.bfsResults[0] = node; + Triebase.bfsResults[1] = suffix; + + return Triebase.bfsResults; + } + private walk( onMatches: (suffix: string[], subdomain: boolean, meta: Meta) => void, initialNode = this.$root, - initialSuffix: string[] = [] + initialSuffix: string[] = [], + withSort = false ) { - const nodeStack: Array> = [initialNode]; + const bfsImpl = withSort ? Triebase.bfsWithSort : Triebase.bfs; + + const nodeStack = new FIFO>(); + nodeStack.enqueue(initialNode); + // Resolving initial string (begin the start of the stack) - const suffixStack: string[][] = [initialSuffix]; + const suffixStack = new FIFO(); + suffixStack.enqueue(initialSuffix); let node: TrieNode = initialNode; + let r; do { - node = nodeStack.pop()!; - const suffix = suffixStack.pop()!; - - node[3].forEach((childNode, k) => { - // Pushing the child node to the stack for next iteration of DFS - nodeStack.push(childNode); - - suffixStack.push([k, ...suffix]); - }); + r = bfsImpl(nodeStack, suffixStack); + node = r[0]!; + const suffix = r[1]; // If the node is a sentinel, we push the suffix to the results if (node[0]) { onMatches(suffix, node[1], node[4]); } - } while (nodeStack.length); + } while (nodeStack.size); }; static compare(this: void, a: string, b: string) { @@ -360,9 +403,9 @@ abstract class Triebase { return true; }; - public dump(onSuffix: (suffix: string) => void): void; - public dump(): string[]; - public dump(onSuffix?: (suffix: string) => void): string[] | void { + public dump(onSuffix: (suffix: string) => void, withSort?: boolean): void; + public dump(onSuffix?: null, withSort?: boolean): string[]; + public dump(onSuffix?: ((suffix: string) => void) | null, withSort = false): string[] | void { const results: string[] = []; const handleSuffix = onSuffix @@ -375,28 +418,36 @@ abstract class Triebase { results.push(subdomain ? '.' + d : d); }; - this.walkWithSort(handleSuffix); + if (withSort) { + this.walkWithSort(handleSuffix); + } else { + this.walk(handleSuffix); + } return results; }; - public dumpMeta(onMeta: (meta: Meta) => void): void; - public dumpMeta(): Meta[]; - public dumpMeta(onMeta?: (meta: Meta) => void): Meta[] | void { + public dumpMeta(onMeta: (meta: Meta) => void, withSort?: boolean): void; + public dumpMeta(onMeta?: null, withSort?: boolean): Meta[]; + public dumpMeta(onMeta?: ((meta: Meta) => void) | null, withSort = false): Meta[] | void { const results: Meta[] = []; const handleMeta = onMeta ? (_suffix: string[], _subdomain: boolean, meta: Meta) => onMeta(meta) : (_suffix: string[], _subdomain: boolean, meta: Meta) => results.push(meta); - this.walk(handleMeta); + if (withSort) { + this.walkWithSort(handleMeta); + } else { + this.walk(handleMeta); + } return results; }; - public dumpWithMeta(onSuffix: (suffix: string, meta: Meta | undefined) => void): void; - public dumpWithMeta(): Array<[string, Meta | undefined]>; - public dumpWithMeta(onSuffix?: (suffix: string, meta: Meta | undefined) => void): Array<[string, Meta | undefined]> | void { + public dumpWithMeta(onSuffix: (suffix: string, meta: Meta | undefined) => void, withSort?: boolean): void; + public dumpWithMeta(onMeta?: null, withSort?: boolean): Array<[string, Meta | undefined]>; + public dumpWithMeta(onSuffix?: ((suffix: string, meta: Meta | undefined) => void) | null, withSort = false): Array<[string, Meta | undefined]> | void { const results: Array<[string, Meta | undefined]> = []; const handleSuffix = onSuffix @@ -409,7 +460,11 @@ abstract class Triebase { results.push([subdomain ? '.' + d : d, meta]); }; - this.walk(handleSuffix); + if (withSort) { + this.walkWithSort(handleSuffix); + } else { + this.walk(handleSuffix); + } return results; };