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.
- package/.github/workflows/node.js.yml +1 -1
- package/.github/workflows/npm-publish.yml +4 -4
- package/lib/natural/brill_pos_tagger/lib/Brill_POS_Tagger.js +33 -31
- package/lib/natural/brill_pos_tagger/lib/Brill_POS_Tester.js +33 -35
- package/lib/natural/brill_pos_tagger/lib/Brill_POS_Trainer.js +326 -324
- package/lib/natural/brill_pos_tagger/lib/Corpus.js +140 -138
- package/lib/natural/brill_pos_tagger/lib/Lexicon.js +78 -76
- package/lib/natural/brill_pos_tagger/lib/Predicate.js +19 -17
- package/lib/natural/brill_pos_tagger/lib/RuleSet.js +58 -56
- package/lib/natural/brill_pos_tagger/lib/RuleTemplate.js +11 -9
- package/lib/natural/brill_pos_tagger/lib/Sentence.js +21 -20
- package/lib/natural/brill_pos_tagger/lib/TransformationRule.js +58 -58
- package/lib/natural/classifiers/bayes_classifier.js +24 -29
- package/lib/natural/classifiers/classifier.js +136 -121
- package/lib/natural/classifiers/logistic_regression_classifier.js +33 -37
- package/lib/natural/classifiers/maxent/Classifier.js +81 -79
- package/lib/natural/classifiers/maxent/Context.js +11 -9
- package/lib/natural/classifiers/maxent/Distribution.js +107 -105
- package/lib/natural/classifiers/maxent/Element.js +12 -10
- package/lib/natural/classifiers/maxent/Feature.js +56 -54
- package/lib/natural/classifiers/maxent/FeatureSet.js +40 -38
- package/lib/natural/classifiers/maxent/GISScaler.js +110 -109
- package/lib/natural/classifiers/maxent/POS/ME_Corpus.js +26 -31
- package/lib/natural/classifiers/maxent/POS/ME_Sentence.js +37 -42
- package/lib/natural/classifiers/maxent/POS/POS_Element.js +119 -124
- package/lib/natural/classifiers/maxent/Sample.js +96 -94
- package/lib/natural/classifiers/maxent/SimpleExample/SE_Element.js +14 -20
- package/lib/natural/inflectors/fr/count_inflector.js +1 -1
- package/lib/natural/inflectors/fr/noun_inflector.js +179 -196
- package/lib/natural/inflectors/ja/noun_inflector.js +67 -84
- package/lib/natural/inflectors/noun_inflector.js +124 -139
- package/lib/natural/inflectors/present_verb_inflector.js +34 -48
- package/lib/natural/inflectors/singular_plural_inflector.js +62 -61
- package/lib/natural/tfidf/tfidf.js +118 -114
- package/lib/natural/trie/trie.js +161 -159
- package/lib/natural/util/directed_edge.js +49 -0
- package/lib/natural/util/edge_weighted_digraph.js +54 -74
- package/lib/natural/util/index.js +1 -0
- package/lib/natural/util/longest_path_tree.js +54 -52
- package/lib/natural/util/shortest_path_tree.js +56 -54
- package/lib/natural/util/topological.js +17 -15
- 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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
98
|
+
const docsWithTerm = this.documents.reduce(function (count, document) {
|
|
99
|
+
return count + (documentHasTerm(term, document) ? 1 : 0)
|
|
100
|
+
}, 0)
|
|
102
101
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
151
|
-
|
|
151
|
+
tfidf (terms, d) {
|
|
152
|
+
const _this = this
|
|
152
153
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
154
|
+
if (!_.isArray(terms)) {
|
|
155
|
+
terms = tokenizer.tokenize(terms.toString().toLowerCase())
|
|
156
|
+
}
|
|
156
157
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
181
|
-
|
|
184
|
+
tfidfs (terms, callback) {
|
|
185
|
+
const tfidfs = new Array(this.documents.length)
|
|
182
186
|
|
|
183
|
-
|
|
184
|
-
|
|
187
|
+
for (let i = 0; i < this.documents.length; i++) {
|
|
188
|
+
tfidfs[i] = this.tfidf(terms, i)
|
|
185
189
|
|
|
186
|
-
|
|
187
|
-
|
|
190
|
+
if (callback) { callback(i, tfidfs[i], this.documents[i].__key) }
|
|
191
|
+
}
|
|
188
192
|
|
|
189
|
-
|
|
193
|
+
return tfidfs
|
|
190
194
|
}
|
|
191
195
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
203
|
-
|
|
202
|
+
// Define a stopwords other than the default
|
|
203
|
+
setStopwords (customStopwords) {
|
|
204
|
+
if (!Array.isArray(customStopwords)) { return false }
|
|
204
205
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
216
|
-
|
|
216
|
+
stopwords = customStopwords
|
|
217
|
+
return true
|
|
218
|
+
}
|
|
217
219
|
}
|
|
220
|
+
|
|
221
|
+
module.exports = TfIdf
|
package/lib/natural/trie/trie.js
CHANGED
|
@@ -25,205 +25,207 @@ THE SOFTWARE.
|
|
|
25
25
|
/**
|
|
26
26
|
* The basis of the TRIE structure.
|
|
27
27
|
**/
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
class Trie {
|
|
29
|
+
constructor (caseSensitive) {
|
|
30
|
+
this.dictionary = Object.create(null) // {}
|
|
31
|
+
this.$ = false
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
49
|
-
if (string.length === 0) {
|
|
50
|
-
const wasWord = this.$
|
|
51
|
-
this.$ = true
|
|
52
|
-
return wasWord
|
|
37
|
+
this.cs = caseSensitive
|
|
53
38
|
}
|
|
54
39
|
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
56
|
+
// Make sure theres a Trie node in our dictionary
|
|
57
|
+
let next = this.dictionary[string[0]]
|
|
66
58
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
return true
|
|
64
|
+
// Continue adding the string
|
|
65
|
+
return next.addString(string.substring(1))
|
|
93
66
|
}
|
|
94
67
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
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
|
-
|
|
114
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
return results
|
|
121
|
-
}
|
|
102
|
+
function recurse (node, stringAgg, resultsAgg) {
|
|
103
|
+
if (!node) return
|
|
122
104
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
133
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
151
|
-
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
return resultsAgg
|
|
133
|
+
if (string.length === 0) {
|
|
134
|
+
return this.$
|
|
172
135
|
}
|
|
173
136
|
|
|
174
|
-
// Otherwise, continue searching
|
|
175
|
-
const
|
|
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
|
|
143
|
+
return false
|
|
178
144
|
}
|
|
179
|
-
return recurse(next, search.substring(1), stringAgg + search[0], resultsAgg)
|
|
180
|
-
};
|
|
181
145
|
|
|
182
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
/**
|