|
| 1 | +/* eslint-disable no-plusplus */ |
| 2 | +/* |
| 3 | +Implemented by watching this conceptually video: https://www.youtube.com/watch?v=VA9m_l6LpwI |
| 4 | +
|
| 5 | +Suffix for banana are : |
| 6 | +banana |
| 7 | +anana |
| 8 | +nana |
| 9 | +ana |
| 10 | +na |
| 11 | +a |
| 12 | +
|
| 13 | +Constructing a suffix tree is O(n*d) where d is length of max string |
| 14 | +
|
| 15 | +Searching a suffix of a string is O(d) where d is length of suffix string. |
| 16 | +If found then return the index, else return -1 |
| 17 | +
|
| 18 | +*/ |
| 19 | + |
| 20 | +class Node { |
| 21 | + constructor(value, isEnd, index) { |
| 22 | + this.data = value; |
| 23 | + this.isEnd = isEnd; |
| 24 | + this.index = index; |
| 25 | + this.next = new Map(); |
| 26 | + } |
| 27 | +} |
| 28 | + |
| 29 | +class SuffixTree { |
| 30 | + constructor(string) { |
| 31 | + this.head = new Node(); |
| 32 | + this.string = string; |
| 33 | + } |
| 34 | + |
| 35 | + constructSuffixTree() { |
| 36 | + const { string } = this; |
| 37 | + let currentString = ''; |
| 38 | + for (let i = string.length - 1; i >= 0; i -= 1) { |
| 39 | + currentString = string[i] + currentString; |
| 40 | + let j = 0; |
| 41 | + let currentNode = this.head; |
| 42 | + while (j < currentString.length) { |
| 43 | + if (!currentNode.next.has(currentString[j])) { |
| 44 | + currentNode.next.set(currentString[j], new Node(currentString, true, i)); |
| 45 | + break; |
| 46 | + } else { |
| 47 | + let k = 0; |
| 48 | + const partialMatchNode = currentNode.next.get(currentString[j]); |
| 49 | + const partialMatchString = partialMatchNode.data; |
| 50 | + |
| 51 | + let matchString = ''; |
| 52 | + while (k < partialMatchString.length && j < currentString.length && partialMatchString[k] === currentString[j]) { |
| 53 | + matchString += currentString[j]; |
| 54 | + k++; |
| 55 | + j++; |
| 56 | + } |
| 57 | + |
| 58 | + let diffString = ''; |
| 59 | + while (k < partialMatchString.length) { |
| 60 | + diffString += partialMatchString[k]; |
| 61 | + k++; |
| 62 | + } |
| 63 | + partialMatchNode.data = matchString; |
| 64 | + if (diffString) { |
| 65 | + partialMatchNode.next.set(diffString[0], new Node(diffString, true, partialMatchNode.index)); |
| 66 | + partialMatchNode.isEnd = false; |
| 67 | + partialMatchNode.index = null; |
| 68 | + } |
| 69 | + |
| 70 | + if (partialMatchNode.next.has(currentString[j])) { |
| 71 | + currentNode = partialMatchNode; |
| 72 | + } else { |
| 73 | + let nextString = ''; |
| 74 | + while (j < currentString.length) { |
| 75 | + nextString += currentString[j]; |
| 76 | + j++; |
| 77 | + } |
| 78 | + partialMatchNode.next.set(nextString[0], new Node(nextString, true, i)); |
| 79 | + break; |
| 80 | + } |
| 81 | + } |
| 82 | + } |
| 83 | + } |
| 84 | + } |
| 85 | + |
| 86 | + findSubstring(string) { |
| 87 | + if (!this.head.next.has(string[0])) { |
| 88 | + return -1; |
| 89 | + } |
| 90 | + |
| 91 | + let currentNode = this.head.next.get(string[0]); |
| 92 | + let currentNodeValue = currentNode.data; |
| 93 | + |
| 94 | + let i = 0; let j = 0; |
| 95 | + |
| 96 | + while (i < string.length) { |
| 97 | + j = 0; |
| 98 | + while (i < string.length && j < currentNodeValue.length && string[i++] === currentNodeValue[j++]); |
| 99 | + |
| 100 | + if (i === string.length && j === currentNodeValue.length && currentNode.isEnd) { |
| 101 | + return currentNode.index; |
| 102 | + } |
| 103 | + |
| 104 | + if (currentNode.next.has(string[i])) { |
| 105 | + currentNode = currentNode.next.get(string[i]); |
| 106 | + currentNodeValue = currentNode.data; |
| 107 | + } else { |
| 108 | + return -1; |
| 109 | + } |
| 110 | + } |
| 111 | + return -1; |
| 112 | + } |
| 113 | +} |
| 114 | + |
| 115 | +// const s = new SuffixTree('banana'); |
| 116 | +// s.constructSuffixTree(); |
| 117 | + |
| 118 | +// console.log(s.findSubstring('nana')); |
| 119 | + |
| 120 | + |
| 121 | +module.exports = SuffixTree; |
0 commit comments