natural 6.10.2 → 6.10.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/.github/workflows/node.js.yml +1 -1
  2. package/.github/workflows/npm-publish.yml +4 -4
  3. package/lib/natural/brill_pos_tagger/lib/Brill_POS_Tagger.js +33 -31
  4. package/lib/natural/brill_pos_tagger/lib/Brill_POS_Tester.js +33 -35
  5. package/lib/natural/brill_pos_tagger/lib/Brill_POS_Trainer.js +326 -324
  6. package/lib/natural/brill_pos_tagger/lib/Corpus.js +140 -138
  7. package/lib/natural/brill_pos_tagger/lib/Lexicon.js +78 -76
  8. package/lib/natural/brill_pos_tagger/lib/Predicate.js +19 -17
  9. package/lib/natural/brill_pos_tagger/lib/RuleSet.js +58 -56
  10. package/lib/natural/brill_pos_tagger/lib/RuleTemplate.js +11 -9
  11. package/lib/natural/brill_pos_tagger/lib/Sentence.js +21 -20
  12. package/lib/natural/brill_pos_tagger/lib/TransformationRule.js +58 -58
  13. package/lib/natural/classifiers/bayes_classifier.js +24 -29
  14. package/lib/natural/classifiers/classifier.js +136 -121
  15. package/lib/natural/classifiers/logistic_regression_classifier.js +33 -37
  16. package/lib/natural/classifiers/maxent/Classifier.js +81 -79
  17. package/lib/natural/classifiers/maxent/Context.js +11 -9
  18. package/lib/natural/classifiers/maxent/Distribution.js +107 -105
  19. package/lib/natural/classifiers/maxent/Element.js +12 -10
  20. package/lib/natural/classifiers/maxent/Feature.js +56 -54
  21. package/lib/natural/classifiers/maxent/FeatureSet.js +40 -38
  22. package/lib/natural/classifiers/maxent/GISScaler.js +110 -109
  23. package/lib/natural/classifiers/maxent/POS/ME_Corpus.js +26 -31
  24. package/lib/natural/classifiers/maxent/POS/ME_Sentence.js +37 -42
  25. package/lib/natural/classifiers/maxent/POS/POS_Element.js +119 -124
  26. package/lib/natural/classifiers/maxent/Sample.js +96 -94
  27. package/lib/natural/classifiers/maxent/SimpleExample/SE_Element.js +14 -20
  28. package/lib/natural/inflectors/fr/count_inflector.js +1 -1
  29. package/lib/natural/inflectors/fr/noun_inflector.js +179 -196
  30. package/lib/natural/inflectors/ja/noun_inflector.js +67 -84
  31. package/lib/natural/inflectors/noun_inflector.js +124 -139
  32. package/lib/natural/inflectors/present_verb_inflector.js +34 -48
  33. package/lib/natural/inflectors/singular_plural_inflector.js +62 -61
  34. package/lib/natural/tfidf/tfidf.js +118 -114
  35. package/lib/natural/trie/trie.js +161 -159
  36. package/lib/natural/util/directed_edge.js +49 -0
  37. package/lib/natural/util/edge_weighted_digraph.js +54 -74
  38. package/lib/natural/util/index.js +1 -0
  39. package/lib/natural/util/longest_path_tree.js +54 -52
  40. package/lib/natural/util/shortest_path_tree.js +56 -54
  41. package/lib/natural/util/topological.js +17 -15
  42. package/package.json +1 -1
@@ -49,20 +49,10 @@ function buildDocument (text, key) {
49
49
  }, { __key: key })
50
50
  }
51
51
 
52
- function tf (term, document) {
53
- return document[term] ? document[term] : 0
54
- }
55
-
56
52
  function documentHasTerm (term, document) {
57
53
  return document[term] && document[term] > 0
58
54
  }
59
55
 
60
- function TfIdf (deserialized) {
61
- if (deserialized) { this.documents = deserialized.documents } else { this.documents = [] }
62
-
63
- this._idfCache = {}
64
- }
65
-
66
56
  // backwards compatibility for < node 0.10
67
57
  function isEncoding (encoding) {
68
58
  if (typeof Buffer.isEncoding !== 'undefined') { return Buffer.isEncoding(encoding) }
@@ -83,135 +73,149 @@ function isEncoding (encoding) {
83
73
  return false
84
74
  }
85
75
 
86
- module.exports = TfIdf
87
- TfIdf.tf = tf
88
-
89
- TfIdf.prototype.idf = function (term, force) {
90
- // Lookup the term in the New term-IDF caching,
91
- // this will cut search times down exponentially on large document sets.
92
- // if (this._idfCache[term] && this._idfCache.hasOwnProperty(term) && force !== true) { return this._idfCache[term] }
93
- if (this._idfCache[term] && force !== true) {
94
- return this._idfCache[term]
76
+ class TfIdf {
77
+ constructor (deserialized) {
78
+ if (deserialized) {
79
+ this.documents = deserialized.documents
80
+ } else {
81
+ this.documents = []
82
+ }
83
+ this._idfCache = {}
84
+ }
85
+
86
+ static tf (term, document) {
87
+ return document[term] ? document[term] : 0
95
88
  }
96
89
 
97
- const docsWithTerm = this.documents.reduce(function (count, document) {
98
- return count + (documentHasTerm(term, document) ? 1 : 0)
99
- }, 0)
90
+ idf (term, force) {
91
+ // Lookup the term in the New term-IDF caching,
92
+ // this will cut search times down exponentially on large document sets.
93
+ // if (this._idfCache[term] && this._idfCache.hasOwnProperty(term) && force !== true) { return this._idfCache[term] }
94
+ if (this._idfCache[term] && force !== true) {
95
+ return this._idfCache[term]
96
+ }
100
97
 
101
- const idf = 1 + Math.log((this.documents.length) / (1 + docsWithTerm))
98
+ const docsWithTerm = this.documents.reduce(function (count, document) {
99
+ return count + (documentHasTerm(term, document) ? 1 : 0)
100
+ }, 0)
102
101
 
103
- // Add the idf to the term cache and return it
104
- this._idfCache[term] = idf
105
- return idf
106
- }
102
+ const idf = 1 + Math.log((this.documents.length) / (1 + docsWithTerm))
103
+
104
+ // Add the idf to the term cache and return it
105
+ this._idfCache[term] = idf
106
+ return idf
107
+ }
107
108
 
108
- // If restoreCache is set to true, all terms idf scores currently cached will be recomputed.
109
- // Otherwise, the cache will just be wiped clean
110
- TfIdf.prototype.addDocument = function (document, key, restoreCache) {
111
- this.documents.push(buildDocument(document, key))
112
-
113
- // make sure the cache is invalidated when new documents arrive
114
- if (restoreCache === true) {
115
- for (const term in this._idfCache) {
116
- // invoking idf with the force option set will
117
- // force a recomputation of the idf, and it will
118
- // automatically refresh the cache value.
119
- this.idf(term, true)
109
+ // If restoreCache is set to true, all terms idf scores currently cached will be recomputed.
110
+ // Otherwise, the cache will just be wiped clean
111
+ addDocument (document, key, restoreCache) {
112
+ this.documents.push(buildDocument(document, key))
113
+
114
+ // make sure the cache is invalidated when new documents arrive
115
+ if (restoreCache === true) {
116
+ for (const term in this._idfCache) {
117
+ // invoking idf with the force option set will
118
+ // force a recomputation of the idf, and it will
119
+ // automatically refresh the cache value.
120
+ this.idf(term, true)
121
+ }
122
+ } else {
123
+ // this._idfCache = {}
124
+ // so that we do not have trouble with terms that match property names
125
+ this._idfCache = Object.create(null)
120
126
  }
121
- } else {
122
- // this._idfCache = {}
123
- // so that we do not have trouble with terms that match property names
124
- this._idfCache = Object.create(null)
125
127
  }
126
- }
127
128
 
128
- // If restoreCache is set to true, all terms idf scores currently cached will be recomputed.
129
- // Otherwise, the cache will just be wiped clean
130
- TfIdf.prototype.addFileSync = function (path, encoding, key, restoreCache) {
131
- if (!encoding) { encoding = 'utf8' }
132
- if (!isEncoding(encoding)) { throw new Error('Invalid encoding: ' + encoding) }
133
-
134
- const document = fs.readFileSync(path, encoding)
135
- this.documents.push(buildDocument(document, key))
136
-
137
- // make sure the cache is invalidated when new documents arrive
138
- if (restoreCache === true) {
139
- for (const term in this._idfCache) {
140
- // invoking idf with the force option set will
141
- // force a recomputation of the idf, and it will
142
- // automatically refresh the cache value.
143
- this.idf(term, true)
129
+ // If restoreCache is set to true, all terms idf scores currently cached will be recomputed.
130
+ // Otherwise, the cache will just be wiped clean
131
+ addFileSync (path, encoding, key, restoreCache) {
132
+ if (!encoding) { encoding = 'utf8' }
133
+ if (!isEncoding(encoding)) { throw new Error('Invalid encoding: ' + encoding) }
134
+
135
+ const document = fs.readFileSync(path, encoding)
136
+ this.documents.push(buildDocument(document, key))
137
+
138
+ // make sure the cache is invalidated when new documents arrive
139
+ if (restoreCache === true) {
140
+ for (const term in this._idfCache) {
141
+ // invoking idf with the force option set will
142
+ // force a recomputation of the idf, and it will
143
+ // automatically refresh the cache value.
144
+ this.idf(term, true)
145
+ }
146
+ } else {
147
+ this._idfCache = {}
144
148
  }
145
- } else {
146
- this._idfCache = {}
147
149
  }
148
- }
149
150
 
150
- TfIdf.prototype.tfidf = function (terms, d) {
151
- const _this = this
151
+ tfidf (terms, d) {
152
+ const _this = this
152
153
 
153
- if (!_.isArray(terms)) {
154
- terms = tokenizer.tokenize(terms.toString().toLowerCase())
155
- }
154
+ if (!_.isArray(terms)) {
155
+ terms = tokenizer.tokenize(terms.toString().toLowerCase())
156
+ }
156
157
 
157
- return terms.reduce(function (value, term) {
158
- let idf = _this.idf(term)
159
- idf = idf === Infinity ? 0 : idf
160
- return value + (tf(term, _this.documents[d]) * idf)
161
- }, 0.0)
162
- }
158
+ return terms.reduce(function (value, term) {
159
+ let idf = _this.idf(term)
160
+ idf = idf === Infinity ? 0 : idf
161
+ return value + (TfIdf.tf(term, _this.documents[d]) * idf)
162
+ }, 0.0)
163
+ }
163
164
 
164
- TfIdf.prototype.listTerms = function (d) {
165
- const terms = []
166
- const _this = this
167
- for (const term in this.documents[d]) {
168
- if (this.documents[d]) {
169
- if (term !== '__key') {
170
- terms.push({
171
- term: term,
172
- tf: tf(term, _this.documents[d]),
173
- idf: _this.idf(term),
174
- tfidf: _this.tfidf(term, d)
175
- })
165
+ listTerms (d) {
166
+ const terms = []
167
+ const _this = this
168
+ for (const term in this.documents[d]) {
169
+ if (this.documents[d]) {
170
+ if (term !== '__key') {
171
+ terms.push({
172
+ term: term,
173
+ tf: TfIdf.tf(term, _this.documents[d]),
174
+ idf: _this.idf(term),
175
+ tfidf: _this.tfidf(term, d)
176
+ })
177
+ }
176
178
  }
177
179
  }
180
+
181
+ return terms.sort(function (x, y) { return y.tfidf - x.tfidf })
178
182
  }
179
183
 
180
- return terms.sort(function (x, y) { return y.tfidf - x.tfidf })
181
- }
184
+ tfidfs (terms, callback) {
185
+ const tfidfs = new Array(this.documents.length)
182
186
 
183
- TfIdf.prototype.tfidfs = function (terms, callback) {
184
- const tfidfs = new Array(this.documents.length)
187
+ for (let i = 0; i < this.documents.length; i++) {
188
+ tfidfs[i] = this.tfidf(terms, i)
185
189
 
186
- for (let i = 0; i < this.documents.length; i++) {
187
- tfidfs[i] = this.tfidf(terms, i)
190
+ if (callback) { callback(i, tfidfs[i], this.documents[i].__key) }
191
+ }
188
192
 
189
- if (callback) { callback(i, tfidfs[i], this.documents[i].__key) }
193
+ return tfidfs
190
194
  }
191
195
 
192
- return tfidfs
193
- }
194
-
195
- // Define a tokenizer other than the default "WordTokenizer"
196
- TfIdf.prototype.setTokenizer = function (t) {
197
- if (!_.isFunction(t.tokenize)) { throw new Error('Expected a valid Tokenizer') }
198
- tokenizer = t
199
- }
196
+ // Define a tokenizer other than the default "WordTokenizer"
197
+ setTokenizer (t) {
198
+ if (!_.isFunction(t.tokenize)) { throw new Error('Expected a valid Tokenizer') }
199
+ tokenizer = t
200
+ }
200
201
 
201
- // Define a stopwords other than the default
202
- TfIdf.prototype.setStopwords = function (customStopwords) {
203
- if (!Array.isArray(customStopwords)) { return false }
202
+ // Define a stopwords other than the default
203
+ setStopwords (customStopwords) {
204
+ if (!Array.isArray(customStopwords)) { return false }
204
205
 
205
- let wrongElement = false
206
- customStopwords.forEach(stopword => {
207
- if ((typeof stopword) !== 'string') {
208
- wrongElement = true
206
+ let wrongElement = false
207
+ customStopwords.forEach(stopword => {
208
+ if ((typeof stopword) !== 'string') {
209
+ wrongElement = true
210
+ }
211
+ })
212
+ if (wrongElement) {
213
+ return false
209
214
  }
210
- })
211
- if (wrongElement) {
212
- return false
213
- }
214
215
 
215
- stopwords = customStopwords
216
- return true
216
+ stopwords = customStopwords
217
+ return true
218
+ }
217
219
  }
220
+
221
+ module.exports = TfIdf
@@ -25,205 +25,207 @@ THE SOFTWARE.
25
25
  /**
26
26
  * The basis of the TRIE structure.
27
27
  **/
28
- function Trie (caseSensitive) {
29
- this.dictionary = Object.create(null) // {}
30
- this.$ = false
28
+ class Trie {
29
+ constructor (caseSensitive) {
30
+ this.dictionary = Object.create(null) // {}
31
+ this.$ = false
31
32
 
32
- if (typeof caseSensitive === 'undefined') {
33
- caseSensitive = true
34
- }
35
-
36
- this.cs = caseSensitive
37
- }
38
-
39
- /**
40
- * Add a single string to the TRIE, returns true if the word was already in the
41
- * trie.
42
- **/
43
- Trie.prototype.addString = function (string) {
44
- if (this.cs === false) {
45
- string = string.toLowerCase()
46
- }
33
+ if (typeof caseSensitive === 'undefined') {
34
+ caseSensitive = true
35
+ }
47
36
 
48
- // If the string has only one letter, mark this as a word.
49
- if (string.length === 0) {
50
- const wasWord = this.$
51
- this.$ = true
52
- return wasWord
37
+ this.cs = caseSensitive
53
38
  }
54
39
 
55
- // Make sure theres a Trie node in our dictionary
56
- let next = this.dictionary[string[0]]
40
+ /**
41
+ * Add a single string to the TRIE, returns true if the word was already in the
42
+ * trie.
43
+ **/
44
+ addString (string) {
45
+ if (this.cs === false) {
46
+ string = string.toLowerCase()
47
+ }
57
48
 
58
- if (!next) {
59
- this.dictionary[string[0]] = new Trie(this.cs)
60
- next = this.dictionary[string[0]]
61
- }
49
+ // If the string has only one letter, mark this as a word.
50
+ if (string.length === 0) {
51
+ const wasWord = this.$
52
+ this.$ = true
53
+ return wasWord
54
+ }
62
55
 
63
- // Continue adding the string
64
- return next.addString(string.substring(1))
65
- }
56
+ // Make sure theres a Trie node in our dictionary
57
+ let next = this.dictionary[string[0]]
66
58
 
67
- /**
68
- * Add multiple strings to the TRIE
69
- **/
70
- Trie.prototype.addStrings = function (list) {
71
- for (const i in list) {
72
- this.addString(list[i])
73
- }
74
- }
75
-
76
- /**
77
- * A function to search the TRIE and return an array of
78
- * words which have same prefix <prefix>
79
- * for example if we had the following words in our database:
80
- * a, ab, bc, cd, abc, abd
81
- * and we search the string: a
82
- * we will get :
83
- * [a, ab, abc, abd]
84
- **/
85
- Trie.prototype.keysWithPrefix = function (prefix) {
86
- if (this.caseSensitive === false) {
87
- prefix = prefix.toLowerCase()
88
- }
59
+ if (!next) {
60
+ this.dictionary[string[0]] = new Trie(this.cs)
61
+ next = this.dictionary[string[0]]
62
+ }
89
63
 
90
- function isEmpty (object) {
91
- for (const key in object) if (object[key]) return false
92
- return true
64
+ // Continue adding the string
65
+ return next.addString(string.substring(1))
93
66
  }
94
67
 
95
- function get (node, word) {
96
- if (!node) return null
97
- if (word.length === 0) return node
98
- return get(node.dictionary[word[0]], word.substring(1))
68
+ /**
69
+ * Add multiple strings to the TRIE
70
+ **/
71
+ addStrings (list) {
72
+ for (const i in list) {
73
+ this.addString(list[i])
74
+ }
99
75
  }
100
76
 
101
- function recurse (node, stringAgg, resultsAgg) {
102
- if (!node) return
103
-
104
- // Check if this is a word
105
- if (node.$) {
106
- resultsAgg.push(stringAgg)
77
+ /**
78
+ * A function to search the TRIE and return an array of
79
+ * words which have same prefix <prefix>
80
+ * for example if we had the following words in our database:
81
+ * a, ab, bc, cd, abc, abd
82
+ * and we search the string: a
83
+ * we will get :
84
+ * [a, ab, abc, abd]
85
+ **/
86
+ keysWithPrefix (prefix) {
87
+ if (this.caseSensitive === false) {
88
+ prefix = prefix.toLowerCase()
107
89
  }
108
90
 
109
- if (isEmpty(node.dictionary)) {
110
- return
91
+ function isEmpty (object) {
92
+ for (const key in object) if (object[key]) return false
93
+ return true
111
94
  }
112
95
 
113
- for (const c in node.dictionary) {
114
- recurse(node.dictionary[c], stringAgg + c, resultsAgg)
96
+ function get (node, word) {
97
+ if (!node) return null
98
+ if (word.length === 0) return node
99
+ return get(node.dictionary[word[0]], word.substring(1))
115
100
  }
116
- }
117
101
 
118
- const results = []
119
- recurse(get(this, prefix), prefix, results)
120
- return results
121
- }
102
+ function recurse (node, stringAgg, resultsAgg) {
103
+ if (!node) return
122
104
 
123
- /**
124
- * A function to search the given string and return true if it lands
125
- * on on a word. Essentially testing if the word exists in the database.
126
- **/
127
- Trie.prototype.contains = function (string) {
128
- if (this.cs === false) {
129
- string = string.toLowerCase()
130
- }
105
+ // Check if this is a word
106
+ if (node.$) {
107
+ resultsAgg.push(stringAgg)
108
+ }
131
109
 
132
- if (string.length === 0) {
133
- return this.$
134
- }
135
-
136
- // Otherwise, we need to continue searching
137
- const firstLetter = string[0]
138
- const next = this.dictionary[firstLetter]
110
+ if (isEmpty(node.dictionary)) {
111
+ return
112
+ }
139
113
 
140
- // If we don't have a node, this isn't a word
141
- if (!next) {
142
- return false
143
- }
144
-
145
- // Otherwise continue the search at the next node
146
- return next.contains(string.substring(1))
147
- }
114
+ for (const c in node.dictionary) {
115
+ recurse(node.dictionary[c], stringAgg + c, resultsAgg)
116
+ }
117
+ }
148
118
 
149
- /**
150
- * A function to search the TRIE and return an array of words which were encountered along the way.
151
- * This will only return words with full prefix matches.
152
- * for example if we had the following words in our database:
153
- * a, ab, bc, cd, abc
154
- * and we searched the string: abcd
155
- * we would get only:
156
- * [a, ab, abc]
157
- **/
158
- Trie.prototype.findMatchesOnPath = function (search) {
159
- if (this.cs === false) {
160
- search = search.toLowerCase()
119
+ const results = []
120
+ recurse(get(this, prefix), prefix, results)
121
+ return results
161
122
  }
162
123
 
163
- function recurse (node, search, stringAgg, resultsAgg) {
164
- // Check if this is a word.
165
- if (node.$) {
166
- resultsAgg.push(stringAgg)
124
+ /**
125
+ * A function to search the given string and return true if it lands
126
+ * on on a word. Essentially testing if the word exists in the database.
127
+ **/
128
+ contains (string) {
129
+ if (this.cs === false) {
130
+ string = string.toLowerCase()
167
131
  }
168
132
 
169
- // Check if the have completed the seearch
170
- if (search.length === 0) {
171
- return resultsAgg
133
+ if (string.length === 0) {
134
+ return this.$
172
135
  }
173
136
 
174
- // Otherwise, continue searching
175
- const next = node.dictionary[search[0]]
137
+ // Otherwise, we need to continue searching
138
+ const firstLetter = string[0]
139
+ const next = this.dictionary[firstLetter]
140
+
141
+ // If we don't have a node, this isn't a word
176
142
  if (!next) {
177
- return resultsAgg
143
+ return false
178
144
  }
179
- return recurse(next, search.substring(1), stringAgg + search[0], resultsAgg)
180
- };
181
145
 
182
- return recurse(this, search, '', [])
183
- }
184
-
185
- /**
186
- * Returns the longest match and the remaining part that could not be matched.
187
- * inspired by [NLTK containers.trie.find_prefix](http://nltk.googlecode.com/svn-/trunk/doc/api/nltk.containers.Trie-class.html).
188
- **/
189
- Trie.prototype.findPrefix = function (search) {
190
- if (this.cs === false) {
191
- search = search.toLowerCase()
146
+ // Otherwise continue the search at the next node
147
+ return next.contains(string.substring(1))
192
148
  }
193
149
 
194
- function recurse (node, search, stringAgg, lastWord) {
195
- // Check if this is a word
196
- if (node.$) {
197
- lastWord = stringAgg
150
+ /**
151
+ * A function to search the TRIE and return an array of words which were encountered along the way.
152
+ * This will only return words with full prefix matches.
153
+ * for example if we had the following words in our database:
154
+ * a, ab, bc, cd, abc
155
+ * and we searched the string: abcd
156
+ * we would get only:
157
+ * [a, ab, abc]
158
+ **/
159
+ findMatchesOnPath (search) {
160
+ if (this.cs === false) {
161
+ search = search.toLowerCase()
198
162
  }
199
163
 
200
- // Check if we have no more to search
201
- if (search.length === 0) {
202
- return [lastWord, search]
203
- }
164
+ function recurse (node, search, stringAgg, resultsAgg) {
165
+ // Check if this is a word.
166
+ if (node.$) {
167
+ resultsAgg.push(stringAgg)
168
+ }
169
+
170
+ // Check if the have completed the seearch
171
+ if (search.length === 0) {
172
+ return resultsAgg
173
+ }
174
+
175
+ // Otherwise, continue searching
176
+ const next = node.dictionary[search[0]]
177
+ if (!next) {
178
+ return resultsAgg
179
+ }
180
+ return recurse(next, search.substring(1), stringAgg + search[0], resultsAgg)
181
+ };
182
+
183
+ return recurse(this, search, '', [])
184
+ }
204
185
 
205
- // Continue searching
206
- const next = node.dictionary[search[0]]
207
- if (!next) {
208
- return [lastWord, search]
186
+ /**
187
+ * Returns the longest match and the remaining part that could not be matched.
188
+ * inspired by [NLTK containers.trie.find_prefix](http://nltk.googlecode.com/svn-/trunk/doc/api/nltk.containers.Trie-class.html).
189
+ **/
190
+ findPrefix (search) {
191
+ if (this.cs === false) {
192
+ search = search.toLowerCase()
209
193
  }
210
- return recurse(next, search.substring(1), stringAgg + search[0], lastWord)
211
- };
212
194
 
213
- return recurse(this, search, '', null)
214
- }
195
+ function recurse (node, search, stringAgg, lastWord) {
196
+ // Check if this is a word
197
+ if (node.$) {
198
+ lastWord = stringAgg
199
+ }
200
+
201
+ // Check if we have no more to search
202
+ if (search.length === 0) {
203
+ return [lastWord, search]
204
+ }
205
+
206
+ // Continue searching
207
+ const next = node.dictionary[search[0]]
208
+ if (!next) {
209
+ return [lastWord, search]
210
+ }
211
+ return recurse(next, search.substring(1), stringAgg + search[0], lastWord)
212
+ };
213
+
214
+ return recurse(this, search, '', null)
215
+ }
215
216
 
216
- /**
217
- * Computes the number of actual nodes from this node to the end.
218
- * Note: This involves traversing the entire structure and may not be
219
- * good for frequent use.
220
- **/
221
- Trie.prototype.getSize = function () {
222
- let total = 1
223
- for (const c in this.dictionary) {
224
- total += this.dictionary[c].getSize()
217
+ /**
218
+ * Computes the number of actual nodes from this node to the end.
219
+ * Note: This involves traversing the entire structure and may not be
220
+ * good for frequent use.
221
+ **/
222
+ getSize () {
223
+ let total = 1
224
+ for (const c in this.dictionary) {
225
+ total += this.dictionary[c].getSize()
226
+ }
227
+ return total
225
228
  }
226
- return total
227
229
  }
228
230
 
229
231
  /**