Skip to content

Commit 5c85a0c

Browse files
committed
Perf: avoid mutate string in Trie
1 parent e53e6b9 commit 5c85a0c

File tree

1 file changed

+38
-26
lines changed

1 file changed

+38
-26
lines changed

Build/lib/trie.ts

+38-26
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,12 @@ function deepTrieNodeToJSON(node: TrieNode,
3838

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

41-
export function hostnameToTokens(hostname: string): string[] {
41+
export function hostnameToTokens(hostname: string, hostnameFromIndex: number): string[] {
4242
const tokens = hostname.split('.');
4343
const results: string[] = [];
4444
let token = '';
4545

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

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

5959
const l = tokens.length - 1;
6060

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

64-
for (let i = l; i >= 0; i--) {
64+
for (let i = l; i >= hostnameFromIndex; i--) {
6565
token = tokens[i];
6666
if (token.length > 0) {
6767
const t = onToken(token);
@@ -104,7 +104,7 @@ abstract class Triebase<Meta = any> {
104104
}
105105
}
106106

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

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

139139
protected walkIntoLeafWithSuffix(
140140
suffix: string,
141+
hostnameFromIndex: number,
141142
onLoop: (node: TrieNode, parent: TrieNode, token: string) => void = noop
142143
) {
143144
let node: TrieNode = this.$root;
@@ -161,18 +162,19 @@ abstract class Triebase<Meta = any> {
161162
return false;
162163
};
163164

164-
if (walkHostnameTokens(suffix, onToken) === null) {
165+
if (walkHostnameTokens(suffix, onToken, hostnameFromIndex) === null) {
165166
return null;
166167
}
167168

168169
return { node, parent };
169170
};
170171

171172
public contains(suffix: string, includeAllSubdomain = suffix[0] === '.'): boolean {
173+
let hostnameFromIndex = 0;
172174
if (suffix[0] === '.') {
173-
suffix = suffix.slice(1);
175+
hostnameFromIndex = 1;
174176
}
175-
const res = this.walkIntoLeafWithSuffix(suffix);
177+
const res = this.walkIntoLeafWithSuffix(suffix, hostnameFromIndex);
176178
if (!res) return false;
177179
if (includeAllSubdomain) return res.node[1];
178180
return true;
@@ -330,14 +332,15 @@ abstract class Triebase<Meta = any> {
330332
*/
331333
public find(
332334
inputSuffix: string,
333-
subdomainOnly = inputSuffix[0] === '.'
335+
subdomainOnly = inputSuffix[0] === '.',
336+
hostnameFromIndex = 0
334337
// /** @default true */ includeEqualWithSuffix = true
335338
): string[] {
336339
if (inputSuffix[0] === '.') {
337-
inputSuffix = inputSuffix.slice(1);
340+
hostnameFromIndex = 1;
338341
}
339342

340-
const inputTokens = hostnameToTokens(inputSuffix);
343+
const inputTokens = hostnameToTokens(inputSuffix, hostnameFromIndex);
341344
const res = this.walkIntoLeafWithTokens(inputTokens);
342345
if (res === null) return [];
343346

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

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

374377
if (!res.node[0]) return false;
@@ -392,11 +395,13 @@ abstract class Triebase<Meta = any> {
392395
* Method used to assert whether the given prefix exists in the Trie.
393396
*/
394397
public has(suffix: string, includeAllSubdomain = suffix[0] === '.'): boolean {
398+
let hostnameFromIndex = 0;
399+
395400
if (suffix[0] === '.') {
396-
suffix = suffix.slice(1);
401+
hostnameFromIndex = 1;
397402
}
398403

399-
const res = this.walkIntoLeafWithSuffix(suffix);
404+
const res = this.walkIntoLeafWithSuffix(suffix, hostnameFromIndex);
400405

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

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

492-
if (suffix[0] === '.') {
493-
suffix = suffix.slice(1);
497+
if (hostnameFromIndex === 0 && suffix[0] === '.') {
498+
hostnameFromIndex = 1;
494499
}
495500

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

514519
// When walkHostnameTokens returns true, we should skip the rest
515-
if (walkHostnameTokens(suffix, onToken)) {
520+
if (walkHostnameTokens(suffix, onToken, hostnameFromIndex)) {
516521
return;
517522
}
518523

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

542-
public whitelist(suffix: string, includeAllSubdomain = suffix[0] === '.') {
547+
public whitelist(suffix: string, includeAllSubdomain = suffix[0] === '.', hostnameFromIndex = 0) {
543548
if (suffix[0] === '.') {
544-
suffix = suffix.slice(1);
549+
hostnameFromIndex = 1;
545550
}
546551

547-
const tokens = hostnameToTokens(suffix);
552+
const tokens = hostnameToTokens(suffix, hostnameFromIndex);
548553
const res = this.getSingleChildLeaf(tokens);
549554

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

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

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

597-
if (suffix[0] === '.') {
598-
suffix = suffix.slice(1);
602+
if (hostnameFromIndex === 0 && suffix[0] === '.') {
603+
hostnameFromIndex = 1;
599604
}
600605

601606
// When walkHostnameTokens returns true, we should skip the rest
602-
if (walkHostnameTokens(suffix, onToken)) {
607+
if (walkHostnameTokens(suffix, onToken, hostnameFromIndex)) {
603608
return;
604609
}
605610

@@ -634,3 +639,10 @@ export type Trie = ReturnType<typeof createTrie>;
634639
// }
635640
// return true;
636641
// };
642+
643+
function subStringEqual(needle: string, haystack: string, needleIndex = 0) {
644+
for (let i = 0, l = haystack.length; i < l; i++) {
645+
if (needle[i + needleIndex] !== haystack[i]) return false;
646+
}
647+
return true;
648+
}

0 commit comments

Comments
 (0)