Skip to content

Commit

Permalink
Perf: avoid mutate string in Trie
Browse files Browse the repository at this point in the history
  • Loading branch information
SukkaW committed Dec 14, 2024
1 parent e53e6b9 commit 5c85a0c
Showing 1 changed file with 38 additions and 26 deletions.
64 changes: 38 additions & 26 deletions Build/lib/trie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ function deepTrieNodeToJSON(node: TrieNode,

const createNode = <Meta = any>(allSubdomain = false, parent: TrieNode | null = null): TrieNode => [false, allSubdomain, parent, new Map<string, TrieNode>(), null] as TrieNode<Meta>;

export function hostnameToTokens(hostname: string): string[] {
export function hostnameToTokens(hostname: string, hostnameFromIndex: number): string[] {
const tokens = hostname.split('.');
const results: string[] = [];
let token = '';

for (let i = 0, l = tokens.length; i < l; i++) {
for (let i = hostnameFromIndex, l = tokens.length; i < l; i++) {
token = tokens[i];
if (token.length > 0) {
results.push(token);
Expand All @@ -53,15 +53,15 @@ export function hostnameToTokens(hostname: string): string[] {
return results;
}

function walkHostnameTokens(hostname: string, onToken: (token: string) => boolean | null): boolean | null {
function walkHostnameTokens(hostname: string, onToken: (token: string) => boolean | null, hostnameFromIndex: number): boolean | null {
const tokens = hostname.split('.');

const l = tokens.length - 1;

// we are at the first of hostname, no splitor there
let token = '';

for (let i = l; i >= 0; i--) {
for (let i = l; i >= hostnameFromIndex; i--) {
token = tokens[i];
if (token.length > 0) {
const t = onToken(token);
Expand Down Expand Up @@ -104,7 +104,7 @@ abstract class Triebase<Meta = any> {
}
}

public abstract add(suffix: string, includeAllSubdomain?: boolean, meta?: Meta): void;
public abstract add(suffix: string, includeAllSubdomain?: boolean, meta?: Meta, hostnameFromIndex?: number): void;

protected walkIntoLeafWithTokens(
tokens: string[],
Expand Down Expand Up @@ -138,6 +138,7 @@ abstract class Triebase<Meta = any> {

protected walkIntoLeafWithSuffix(
suffix: string,
hostnameFromIndex: number,
onLoop: (node: TrieNode, parent: TrieNode, token: string) => void = noop
) {
let node: TrieNode = this.$root;
Expand All @@ -161,18 +162,19 @@ abstract class Triebase<Meta = any> {
return false;
};

if (walkHostnameTokens(suffix, onToken) === null) {
if (walkHostnameTokens(suffix, onToken, hostnameFromIndex) === null) {
return null;
}

return { node, parent };
};

public contains(suffix: string, includeAllSubdomain = suffix[0] === '.'): boolean {
let hostnameFromIndex = 0;
if (suffix[0] === '.') {
suffix = suffix.slice(1);
hostnameFromIndex = 1;
}
const res = this.walkIntoLeafWithSuffix(suffix);
const res = this.walkIntoLeafWithSuffix(suffix, hostnameFromIndex);
if (!res) return false;
if (includeAllSubdomain) return res.node[1];
return true;
Expand Down Expand Up @@ -330,14 +332,15 @@ abstract class Triebase<Meta = any> {
*/
public find(
inputSuffix: string,
subdomainOnly = inputSuffix[0] === '.'
subdomainOnly = inputSuffix[0] === '.',
hostnameFromIndex = 0
// /** @default true */ includeEqualWithSuffix = true
): string[] {
if (inputSuffix[0] === '.') {
inputSuffix = inputSuffix.slice(1);
hostnameFromIndex = 1;
}

const inputTokens = hostnameToTokens(inputSuffix);
const inputTokens = hostnameToTokens(inputSuffix, hostnameFromIndex);
const res = this.walkIntoLeafWithTokens(inputTokens);
if (res === null) return [];

Expand All @@ -346,7 +349,7 @@ abstract class Triebase<Meta = any> {
const onMatches = subdomainOnly
? (suffix: string[], subdomain: boolean) => { // fast path (default option)
const d = fastStringArrayJoin(suffix, '.');
if (!subdomain && d === inputSuffix) return;
if (!subdomain && subStringEqual(inputSuffix, d, 1)) return;

results.push(subdomain ? '.' + d : d);
}
Expand All @@ -368,7 +371,7 @@ abstract class Triebase<Meta = any> {
* Method used to delete a prefix from the trie.
*/
public remove(suffix: string): boolean {
const res = this.getSingleChildLeaf(hostnameToTokens(suffix));
const res = this.getSingleChildLeaf(hostnameToTokens(suffix, 0));
if (res === null) return false;

if (!res.node[0]) return false;
Expand All @@ -392,11 +395,13 @@ abstract class Triebase<Meta = any> {
* Method used to assert whether the given prefix exists in the Trie.
*/
public has(suffix: string, includeAllSubdomain = suffix[0] === '.'): boolean {
let hostnameFromIndex = 0;

if (suffix[0] === '.') {
suffix = suffix.slice(1);
hostnameFromIndex = 1;
}

const res = this.walkIntoLeafWithSuffix(suffix);
const res = this.walkIntoLeafWithSuffix(suffix, hostnameFromIndex);

if (res === null) return false;
if (!res.node[0]) return false;
Expand Down Expand Up @@ -485,12 +490,12 @@ abstract class Triebase<Meta = any> {
export class HostnameSmolTrie<Meta = any> extends Triebase<Meta> {
public smolTree = true;

add(suffix: string, includeAllSubdomain = suffix[0] === '.', meta?: Meta): void {
add(suffix: string, includeAllSubdomain = suffix[0] === '.', meta?: Meta, hostnameFromIndex = 0): void {
let node: TrieNode<Meta> = this.$root;
let curNodeChildren: Map<string, TrieNode<Meta>> = node[3];

if (suffix[0] === '.') {
suffix = suffix.slice(1);
if (hostnameFromIndex === 0 && suffix[0] === '.') {
hostnameFromIndex = 1;
}

const onToken = (token: string) => {
Expand All @@ -512,7 +517,7 @@ export class HostnameSmolTrie<Meta = any> extends Triebase<Meta> {
};

// When walkHostnameTokens returns true, we should skip the rest
if (walkHostnameTokens(suffix, onToken)) {
if (walkHostnameTokens(suffix, onToken, hostnameFromIndex)) {
return;
}

Expand All @@ -539,12 +544,12 @@ export class HostnameSmolTrie<Meta = any> extends Triebase<Meta> {
node[4] = meta!;
}

public whitelist(suffix: string, includeAllSubdomain = suffix[0] === '.') {
public whitelist(suffix: string, includeAllSubdomain = suffix[0] === '.', hostnameFromIndex = 0) {
if (suffix[0] === '.') {
suffix = suffix.slice(1);
hostnameFromIndex = 1;
}

const tokens = hostnameToTokens(suffix);
const tokens = hostnameToTokens(suffix, hostnameFromIndex);
const res = this.getSingleChildLeaf(tokens);

if (res === null) return;
Expand Down Expand Up @@ -579,7 +584,7 @@ export class HostnameTrie<Meta = any> extends Triebase<Meta> {
return this.$size;
}

add(suffix: string, includeAllSubdomain = suffix[0] === '.', meta?: Meta): void {
add(suffix: string, includeAllSubdomain = suffix[0] === '.', meta?: Meta, hostnameFromIndex = 0): void {
let node: TrieNode<Meta> = this.$root;

const onToken = (token: string) => {
Expand All @@ -594,12 +599,12 @@ export class HostnameTrie<Meta = any> extends Triebase<Meta> {
return false;
};

if (suffix[0] === '.') {
suffix = suffix.slice(1);
if (hostnameFromIndex === 0 && suffix[0] === '.') {
hostnameFromIndex = 1;
}

// When walkHostnameTokens returns true, we should skip the rest
if (walkHostnameTokens(suffix, onToken)) {
if (walkHostnameTokens(suffix, onToken, hostnameFromIndex)) {
return;
}

Expand Down Expand Up @@ -634,3 +639,10 @@ export type Trie = ReturnType<typeof createTrie>;
// }
// return true;
// };

function subStringEqual(needle: string, haystack: string, needleIndex = 0) {
for (let i = 0, l = haystack.length; i < l; i++) {
if (needle[i + needleIndex] !== haystack[i]) return false;
}
return true;
}

0 comments on commit 5c85a0c

Please sign in to comment.