const fs = require('fs'); UNDEFINED = -1; LEAF_NODE = Number.MAX_SAFE_INTEGER; class SuffixTree { constructor(aWord) { this.text = Array.from(aWord); this.text.push('\uF123'); // Terminal character this.nodes = new Array(2 * this.text.length); this.root = this.newNode(this.UNDEFINED, this.UNDEFINED); this.activeNode = this.root; this.textIndex = 0; this.currentNode = 0; this.needParentLink = this.UNDEFINED; this.remainder = 0; this.leafIndexGenerator = 0; for (let i = 0; i < this.text.length; i++) { this.extendSuffixTree(this.text[i]); } } getLongestRepeatedSubstrings() { const indexes = this.doTraversal(); const word = this.text.slice(0, this.text.length - 1).join(''); const result = {}; if (indexes[0] > 0) { for (let i = 1; i < indexes.length; i++) { const substring = word.substring(indexes[i], indexes[i] + indexes[0]); if (!result[substring]) { result[substring] = new Set(); } result[substring].add(indexes[i]); } } return result; } extendSuffixTree(aCharacter) { this.needParentLink = this.UNDEFINED; this.remainder++; while (this.remainder > 0) { if (this.activeLength === 0) { this.activeEdge = this.textIndex; } if (!this.nodes[this.activeNode].children.hasOwnProperty(this.text[this.activeEdge])) { const leaf = this.newNode(this.textIndex, this.LEAF_NODE); this.nodes[this.activeNode].children[this.text[this.activeEdge]] = leaf; this.addSuffixLink(this.activeNode); } else { const next = this.nodes[this.activeNode].children[this.text[this.activeEdge]]; if (this.walkDown(next)) { continue; } if (this.text[this.nodes[next].start + this.activeLength] === aCharacter) { this.activeLength++; this.addSuffixLink(this.activeNode); break; } const split = this.newNode(this.nodes[next].start, this.nodes[next].start + this.activeLength); this.nodes[this.activeNode].children[this.text[this.activeEdge]] = split; const leaf = this.newNode(this.textIndex, this.LEAF_NODE); this.nodes[split].children[aCharacter] = leaf; this.nodes[next].start += this.activeLength; this.nodes[split].children[this.text[this.nodes[next].start]] = next; this.addSuffixLink(split); } this.remainder--; if (this.activeNode === this.root && this.activeLength > 0) { this.activeLength--; this.activeEdge = this.textIndex - this.remainder + 1; } else { this.activeNode = (this.nodes[this.activeNode].parentLink > 0) ? this.nodes[this.activeNode].parentLink : this.root; } } this.textIndex++; } walkDown(aNode) { if (this.activeLength >= this.nodes[aNode].edgeLength()) { this.activeEdge += this.nodes[aNode].edgeLength(); this.activeLength -= this.nodes[aNode].edgeLength(); this.activeNode = aNode; return true; } return false; } addSuffixLink(aNode) { if (this.needParentLink !== this.UNDEFINED) { this.nodes[this.needParentLink].parentLink = aNode; } this.needParentLink = aNode; } newNode(aStart, aEnd) { const node = new Node(aStart, aEnd); node.leafIndex = (aEnd === this.LEAF_NODE) ? this.leafIndexGenerator++ : this.UNDEFINED; this.nodes[this.currentNode] = node; return this.currentNode++; } doTraversal() { const indexes = [this.UNDEFINED]; return this.traversal(indexes, this.nodes[this.root], 0); } traversal(aIndexes, aNode, aHeight) { if (aNode.leafIndex === this.UNDEFINED) { for (const index of Object.values(aNode.children)) { const child = this.nodes[index]; this.traversal(aIndexes, child, aHeight + child.edgeLength()); } } else if (aIndexes[0] < aHeight - aNode.edgeLength()) { aIndexes.length = 0; aIndexes.push(aHeight - aNode.edgeLength()); aIndexes.push(aNode.leafIndex); } else if (aIndexes[0] === aHeight - aNode.edgeLength()) { aIndexes.push(aNode.leafIndex); } return aIndexes; } } class Node { constructor(aStart, aEnd) { this.start = aStart; this.end = aEnd; this.parentLink = 0; this.leafIndex = 0; this.children = {}; } edgeLength() { return Math.min(this.end, suffixTree.textIndex + 1) - this.start; } } async function main() { const limits = [1000, 10000, 100000]; const piDigitsFile = 'piDigits.txt'; try { const contents = await fs.promises.readFile(piDigitsFile, 'utf8'); for (const limit of limits) { const piDigits = contents.substring(0, limit + 1); const start = Date.now(); suffixTree = new SuffixTree(piDigits); const substrings = suffixTree.getLongestRepeatedSubstrings(); const end = Date.now(); console.log(`First ${limit} digits of pi has longest repeated characters:`); for (const substring in substrings) { if (substrings.hasOwnProperty(substring)) { const indexes = Array.from(substrings[substring]); console.log(` '${substring}' starting at index ${indexes.join(' and ')}`); } } console.log(`Time taken: ${end - start} milliseconds.\n`); } console.log("The timings show that the implementation has approximately linear performance."); } catch (err) { console.error("An error occurred:", err); } } let suffixTree; main();