natural 0.5.0 → 0.5.1

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/.travis.yml CHANGED
@@ -1,5 +1,4 @@
1
1
  language: node_js
2
2
  node_js:
3
- - "0.8"
4
3
  - "0.10"
5
- before_install: 'npm install -g jasmine-node'
4
+ before_install: 'npm install -g jasmine-node'
File without changes
package/README.md CHANGED
@@ -289,6 +289,15 @@ var restoredClassifier = natural.BayesClassifier.restore(JSON.parse(raw));
289
289
  console.log(restoredClassifier.classify('i should sell that'));
290
290
  ```
291
291
 
292
+ __Note:__ if using the classifier for languages other than English you may need
293
+ to pass in the stemmer to use. In fact, you can do this for any stemmer including
294
+ alternate English stemmers. The default is the `PorterStemmer`.
295
+
296
+ ```javascript
297
+ const PorterStemmerRu = require('./node_modules/natural/lib/natural/stemmers/porter_stemmer_ru');
298
+ var classifier = new natural.BayesClassifier(PorterStemmerRu);
299
+ ```
300
+
292
301
  ## Phonetics
293
302
 
294
303
  Phonetic matching (sounds-like) matching can be done with the [SoundEx](http://en.wikipedia.org/wiki/Soundex),
@@ -954,7 +963,7 @@ This is best constructed with an array of tokens from a corpus, but a simple lis
954
963
 
955
964
  ```javascript
956
965
  var corpus = ['something', 'soothing'];
957
- var spellcheck = new Spellcheck(corpus);
966
+ var spellcheck = new natural.Spellcheck(corpus);
958
967
  ```
959
968
 
960
969
  It uses the trie datastructure for fast boolean lookup of a word
@@ -977,19 +986,22 @@ algorithm. Transformation rules are specified in external files.
977
986
 
978
987
  ### Usage
979
988
  ```javascript
980
- var natural = require("./lib/natural");
989
+ var natural = require("natural");
990
+ var path = require("path");
981
991
 
982
- var base_folder = "some_path/lib/natural/brill_pos_tagger";
983
- var rulesFilename = base_folder + "/data/tr_from_posjs.txt";
984
- var lexiconFilename = base_folder + "/data/lexicon_from_posjs.json";
992
+ var base_folder = path.join(path.dirname(require.resolve("natural")), "brill_pos_tagger");
993
+ var rulesFilename = base_folder + "/data/English/tr_from_posjs.txt";
994
+ var lexiconFilename = base_folder + "/data/English/lexicon_from_posjs.json";
985
995
  var defaultCategory = 'N';
986
996
 
987
997
  var lexicon = new natural.Lexicon(lexiconFilename, defaultCategory);
988
- var rules = new natural.Ruleset(rulesFilename);
998
+ var rules = new natural.RuleSet(rulesFilename);
989
999
  var tagger = new natural.BrillPOSTagger(lexicon, rules);
990
1000
 
991
1001
  var sentence = ["I", "see", "the", "man", "with", "the", "telescope"];
992
1002
  console.log(JSON.stringify(tagger.tag(sentence)));
1003
+ // [["I","NN"],["see","VB"],["the","DT"],["man","NN"],["with","IN"],["the","DT"],["telescope","NN"]]
1004
+
993
1005
  ```
994
1006
 
995
1007
  ### Lexicon
@@ -0,0 +1 @@
1
+ {"classifier":{"classFeatures":{"computing":{"0":2,"1":2,"2":2,"3":3,"4":2,"5":2},"literature":{"2":2,"6":4,"7":2,"8":2}},"classTotals":{"computing":4,"literature":4},"totalExamples":7,"smoothing":1},"docs":[{"label":"computing","text":["fix","box"]},{"label":"computing","text":["write","code"]},{"label":"computing","text":["nasti","script","code"]},{"label":"literature","text":["write","book"]},{"label":"literature","text":["read","book"]},{"label":"literature","text":["studi","book"]}],"features":{"fix":1,"box":1,"write":2,"code":2,"nasti":1,"script":1,"book":3,"read":1,"studi":1},"stemmer":{},"lastAdded":6,"events":{"domain":null,"_events":{},"_eventsCount":0}}
@@ -45,7 +45,7 @@ function restore(classifier, stemmer) {
45
45
  function load(filename, stemmer, callback) {
46
46
  Classifier.load(filename, function(err, classifier) {
47
47
  if (err) {
48
- callback(err);
48
+ return callback(err);
49
49
  }
50
50
  else {
51
51
  callback(err, restore(classifier, stemmer));
@@ -21,8 +21,10 @@ THE SOFTWARE.
21
21
  */
22
22
 
23
23
  var PorterStemmer = require('../stemmers/porter_stemmer'),
24
+ Threads = require('webworker-threads'),
24
25
  util = require('util'),
25
- events = require('events');
26
+ events = require('events')
27
+ os = require('os');
26
28
 
27
29
  var Classifier = function(classifier, stemmer) {
28
30
  this.classifier = classifier;
@@ -34,15 +36,15 @@ var Classifier = function(classifier, stemmer) {
34
36
  };
35
37
 
36
38
  function addDocument(text, classification) {
37
-
38
- // Ignore further processing if classification is undefined
39
+
40
+ // Ignore further processing if classification is undefined
39
41
  if(typeof classification === 'undefined') return;
40
-
41
- // If classification is type of string then make sure it's dosen't have blank space at both end
42
+
43
+ // If classification is type of string then make sure it's dosen't have blank space at both end
42
44
  if(typeof classification === 'string'){
43
45
  classification = classification.trim();
44
46
  }
45
-
47
+
46
48
  if(typeof text === 'string')
47
49
  text = this.stemmer.tokenizeAndStem(text);
48
50
 
@@ -105,6 +107,28 @@ function textToFeatures(observation) {
105
107
  return features;
106
108
  }
107
109
 
110
+ function docsToFeatures(docs) {
111
+ var parsedDocs = [];
112
+
113
+ for (var i = 0; i < docs.length; i++) {
114
+ var features = [];
115
+
116
+ for (var feature in FEATURES) {
117
+ if (docs[i].observation.indexOf(feature) > -1)
118
+ features.push(1);
119
+ else
120
+ features.push(0);
121
+ }
122
+
123
+ parsedDocs.push({
124
+ index: docs[i].index,
125
+ features: features
126
+ });
127
+ }
128
+
129
+ return JSON.stringify(parsedDocs);
130
+ }
131
+
108
132
  function train() {
109
133
  var totalDocs = this.docs.length;
110
134
  for(var i = this.lastAdded; i < totalDocs; i++) {
@@ -117,12 +141,246 @@ function train() {
117
141
  this.classifier.train();
118
142
  }
119
143
 
144
+ function trainParallel(numThreads, callback) {
145
+ if (!callback) {
146
+ callback = numThreads;
147
+ numThreads = undefined;
148
+ }
149
+
150
+ if (isNaN(numThreads)) {
151
+ numThreads = os.cpus().length;
152
+ }
153
+
154
+ var totalDocs = this.docs.length;
155
+ var threadPool = Threads.createPool(numThreads);
156
+ var docFeatures = {};
157
+ var finished = 0;
158
+ var self = this;
159
+
160
+ // Init pool; send the features array and the parsing function
161
+ threadPool.all.eval('var FEATURES = ' + JSON.stringify(this.features));
162
+ threadPool.all.eval(docsToFeatures);
163
+
164
+ // Convert docs to observation objects
165
+ var obsDocs = [];
166
+ for (var i = this.lastAdded; i < totalDocs; i++) {
167
+ var observation = this.docs[i].text;
168
+ if (typeof observation === 'string')
169
+ observation = this.stemmer.tokenizeAndStem(observation);
170
+ obsDocs.push({
171
+ index: i,
172
+ observation: observation
173
+ });
174
+ }
175
+
176
+ // Called when a batch completes processing
177
+ var onFeaturesResult = function(docs) {
178
+ setTimeout(function() {
179
+ self.events.emit('processedBatch', {
180
+ size: docs.length,
181
+ docs: totalDocs,
182
+ batches: numThreads,
183
+ index: finished
184
+ });
185
+ });
186
+
187
+ for (var j = 0; j < docs.length; j++) {
188
+ docFeatures[docs[j].index] = docs[j].features;
189
+ }
190
+ };
191
+
192
+ // Called when all batches finish processing
193
+ var onFinished = function(err) {
194
+ if (err) {
195
+ threadPool.destroy();
196
+ return callback(err);
197
+ }
198
+
199
+ for (var j = self.lastAdded; j < totalDocs; j++) {
200
+ self.classifier.addExample(docFeatures[j], self.docs[j].label);
201
+ self.events.emit('trainedWithDocument', {
202
+ index: j,
203
+ total: totalDocs,
204
+ doc: self.docs[j]
205
+ });
206
+ self.lastAdded++;
207
+ }
208
+
209
+ self.events.emit('doneTraining', true);
210
+ self.classifier.train();
211
+
212
+ threadPool.destroy();
213
+ callback(null);
214
+ };
215
+
216
+ // Split the docs and start processing
217
+ var batchSize = Math.ceil(obsDocs.length / numThreads);
218
+ var lastError;
219
+
220
+ for (var i = 0; i < numThreads; i++) {
221
+ var batchDocs = obsDocs.slice(i * batchSize, (i+1) * batchSize);
222
+ var batchJson = JSON.stringify(batchDocs);
223
+
224
+ threadPool.any.eval('docsToFeatures(' + batchJson + ')', function(err, docs) {
225
+ lastError = err || lastError;
226
+ finished++;
227
+
228
+ if (docs) {
229
+ docs = JSON.parse(docs);
230
+ onFeaturesResult(docs);
231
+ }
232
+
233
+ if (finished >= numThreads) {
234
+ onFinished(lastError);
235
+ }
236
+ });
237
+ }
238
+ }
239
+
240
+ function trainParallelBatches(options) {
241
+ var numThreads = options && options.numThreads;
242
+ var batchSize = options && options.batchSize;
243
+
244
+ if (isNaN(numThreads)) {
245
+ numThreads = os.cpus().length;
246
+ }
247
+
248
+ if (isNaN(batchSize)) {
249
+ batchSize = 2500;
250
+ }
251
+
252
+ var totalDocs = this.docs.length;
253
+ var threadPool = Threads.createPool(numThreads);
254
+ var docFeatures = {};
255
+ var finished = 0;
256
+ var self = this;
257
+
258
+ var abort = false;
259
+ var onError = function(err) {
260
+ if (!err || abort) return;
261
+ abort = true;
262
+ threadPool.destroy(true);
263
+ self.events.emit('doneTrainingError', err);
264
+ };
265
+
266
+ // Init pool; send the features array and the parsing function
267
+ var str = JSON.stringify(this.features);
268
+ threadPool.all.eval('var FEATURES = ' + str + ';', onError);
269
+ threadPool.all.eval(docsToFeatures, onError);
270
+
271
+ // Convert docs to observation objects
272
+ var obsDocs = [];
273
+ for (var i = this.lastAdded; i < totalDocs; i++) {
274
+ var observation = this.docs[i].text;
275
+ if (typeof observation === 'string')
276
+ observation = this.stemmer.tokenizeAndStem(observation);
277
+ obsDocs.push({
278
+ index: i,
279
+ observation: observation
280
+ });
281
+ }
282
+
283
+ // Split the docs in batches
284
+ var obsBatches = [];
285
+ var i = 0;
286
+ while (true) {
287
+ var batch = obsDocs.slice(i * batchSize, (i+1) * batchSize);
288
+ if (!batch || !batch.length) break;
289
+ obsBatches.push(batch);
290
+ i++;
291
+ }
292
+ obsDocs = null;
293
+ self.events.emit('startedTraining', {
294
+ docs: totalDocs,
295
+ batches: obsBatches.length
296
+ });
297
+
298
+ // Called when a batch completes processing
299
+ var onFeaturesResult = function(docs) {
300
+ self.events.emit('processedBatch', {
301
+ size: docs.length,
302
+ docs: totalDocs,
303
+ batches: obsBatches.length,
304
+ index: finished
305
+ });
306
+
307
+ for (var j = 0; j < docs.length; j++) {
308
+ docFeatures[docs[j].index] = docs[j].features;
309
+ }
310
+ };
311
+
312
+ // Called when all batches finish processing
313
+ var onFinished = function() {
314
+ threadPool.destroy(true);
315
+ abort = true;
316
+
317
+ for (var j = self.lastAdded; j < totalDocs; j++) {
318
+ self.classifier.addExample(docFeatures[j], self.docs[j].label);
319
+ self.events.emit('trainedWithDocument', {
320
+ index: j,
321
+ total: totalDocs,
322
+ doc: self.docs[j]
323
+ });
324
+ self.lastAdded++;
325
+ }
326
+
327
+ self.events.emit('doneTraining', true);
328
+ self.classifier.train();
329
+ };
330
+
331
+ // Called to send the next batch to be processed
332
+ var batchIndex = 0;
333
+ var sendNext = function() {
334
+ if (abort) return;
335
+ if (batchIndex >= obsBatches.length) {
336
+ return;
337
+ }
338
+
339
+ sendBatch(JSON.stringify(obsBatches[batchIndex]));
340
+ batchIndex++;
341
+ };
342
+
343
+ // Called to send a batch of docs to the threads
344
+ var sendBatch = function(batchJson) {
345
+ if (abort) return;
346
+ threadPool.any.eval('docsToFeatures(' + batchJson + ');', function(err, docs) {
347
+ if (err) {
348
+ return onError(err);
349
+ }
350
+
351
+ finished++;
352
+
353
+ if (docs) {
354
+ docs = JSON.parse(docs);
355
+ setTimeout(onFeaturesResult.bind(null, docs));
356
+ }
357
+
358
+ if (finished >= obsBatches.length) {
359
+ setTimeout(onFinished);
360
+ }
361
+
362
+ setTimeout(sendNext);
363
+ });
364
+ };
365
+
366
+ // Start processing
367
+ for (var i = 0; i < numThreads; i++) {
368
+ sendNext();
369
+ }
370
+ }
371
+
120
372
  function retrain() {
121
373
  this.classifier = new (this.classifier.constructor)();
122
374
  this.lastAdded = 0;
123
375
  this.train();
124
376
  }
125
377
 
378
+ function retrainParallel(numThreads, callback) {
379
+ this.classifier = new (this.classifier.constructor)();
380
+ this.lastAdded = 0;
381
+ this.trainParallel(numThreads, callback);
382
+ }
383
+
126
384
  function getClassifications(observation) {
127
385
  return this.classifier.getClassifications(this.textToFeatures(observation));
128
386
  }
@@ -153,7 +411,7 @@ function load(filename, callback) {
153
411
 
154
412
  fs.readFile(filename, 'utf8', function(err, data) {
155
413
  var classifier;
156
-
414
+
157
415
  if(!err) {
158
416
  classifier = JSON.parse(data);
159
417
  }
@@ -166,7 +424,10 @@ function load(filename, callback) {
166
424
  Classifier.prototype.addDocument = addDocument;
167
425
  Classifier.prototype.removeDocument = removeDocument;
168
426
  Classifier.prototype.train = train;
427
+ Classifier.prototype.trainParallel = trainParallel;
428
+ Classifier.prototype.trainParallelBatches = trainParallelBatches;
169
429
  Classifier.prototype.retrain = retrain;
430
+ Classifier.prototype.retrainParallel = retrainParallel;
170
431
  Classifier.prototype.classify = classify;
171
432
  Classifier.prototype.textToFeatures = textToFeatures;
172
433
  Classifier.prototype.save = save;
@@ -27,70 +27,71 @@ THE SOFTWARE.
27
27
  // s1 is the first string to compare
28
28
  // s2 is the second string to compare
29
29
  function distance(s1, s2) {
30
- if (typeof(s1) != "string" || typeof(s2) != "string") return 0;
31
- if (s1.length == 0 || s2.length == 0)
30
+ if (typeof(s1) !== "string" || typeof(s2) !== "string") {
32
31
  return 0;
32
+ }
33
+
34
+ if (s1.length === 0 || s2.length === 0) {
35
+ return 0;
36
+ }
37
+
33
38
  s1 = s1.toLowerCase(), s2 = s2.toLowerCase();
39
+
34
40
  var matchWindow = (Math.floor(Math.max(s1.length, s2.length) / 2.0)) - 1;
35
41
  var matches1 = new Array(s1.length);
36
42
  var matches2 = new Array(s2.length);
37
43
  var m = 0; // number of matches
38
44
  var t = 0; // number of transpositions
45
+ var i = 0; // index for string 1
46
+ var k = 0; // index for string 2
39
47
 
40
48
  //debug helpers
41
49
  //console.log("s1: " + s1 + "; s2: " + s2);
42
50
  //console.log(" - matchWindow: " + matchWindow);
43
51
 
44
- // find matches
45
- for (var i = 0; i < s1.length; i++) {
46
- var matched = false;
47
-
48
- // check for an exact match
49
- if (s1[i] == s2[i]) {
50
- matches1[i] = matches2[i] = matched = true;
51
- m++
52
- }
53
-
54
- // check the "match window"
55
- else {
56
- // this for loop is a little brutal
57
- for (k = (i <= matchWindow) ? 0 : i - matchWindow;
58
- (k <= i + matchWindow) && k < s2.length && !matched;
59
- k++) {
60
- if (s1[i] == s2[k]) {
61
- if(!matches1[i] && !matches2[k]) {
62
- m++;
63
- }
64
-
65
- matches1[i] = matches2[k] = matched = true;
66
- }
67
- }
68
- }
69
- }
52
+ for (i = 0; i < s1.length; i++) { // loop to find matched characters
53
+ var start = Math.max(0, (i - matchWindow)); // use the higher of the window diff
54
+ var end = Math.min((i + matchWindow + 1), s2.length); // use the min of the window and string 2 length
70
55
 
71
- if(m == 0)
72
- return 0.0;
56
+ for (k = start; k < end; k++) { // iterate second string index
57
+ if (matches2[k]) { // if second string character already matched
58
+ continue;
59
+ }
60
+ if (s1[i] !== s2[k]) { // characters don't match
61
+ continue;
62
+ }
73
63
 
74
- // count transpositions
75
- var k = 0;
64
+ // assume match if the above 2 checks don't continue
65
+ matches1[i] = true;
66
+ matches2[k] = true;
67
+ m++;
68
+ break;
69
+ }
70
+ }
76
71
 
77
- for(var i = 0; i < s1.length; i++) {
78
- if(matches1[k]) {
79
- while(!matches2[k] && k < matches2.length)
80
- k++;
81
- if(s1[i] != s2[k] && k < matches2.length) {
82
- t++;
83
- }
72
+ // nothing matched
73
+ if (m === 0) {
74
+ return 0.0;
75
+ }
84
76
 
85
- k++;
86
- }
77
+ k = 0; // reset string 2 index
78
+ for(i = 0; i < s1.length; i++) { // loop to find transpositions
79
+ if (!matches1[i]) { // non-matching character
80
+ continue;
81
+ }
82
+ while(!matches2[k]) { // move k index to the next match
83
+ k++;
84
+ }
85
+ if (s1[i] !== s2[k]) { // if the characters don't match, increase transposition
86
+ t++;
87
+ }
88
+ k++; // iterate k index normally
87
89
  }
88
-
89
- //debug helpers:
90
- //console.log(" - matches: " + m);
91
- //console.log(" - transpositions: " + t);
90
+
91
+ // transpositions divided by 2
92
92
  t = t / 2.0;
93
- return (m / s1.length + m / s2.length + (m - t) / m) / 3;
93
+
94
+ return ((m / s1.length) + (m / s2.length) + ((m - t) / m)) / 3.0;
94
95
  }
95
96
 
96
97
  // Computes the Winkler distance between two string -- intrepreted from:
@@ -99,18 +100,18 @@ function distance(s1, s2) {
99
100
  // s2 is the second string to compare
100
101
  // dj is the Jaro Distance (if you've already computed it), leave blank and the method handles it
101
102
  function JaroWinklerDistance(s1, s2, dj) {
102
- if (s1 == s2) {
103
- return 1
104
- }
105
- else {
106
- var jaro;
107
- (typeof(dj) == 'undefined')? jaro = distance(s1,s2) : jaro = dj;
108
- var p = 0.1; //
109
- var l = 0 // length of the matching prefix
110
- while(s1[l] == s2[l] && l < 4)
111
- l++;
112
-
113
- return jaro + l * p * (1 - jaro);
114
- }
103
+ if (s1 === s2) {
104
+ return 1;
105
+ } else {
106
+ var jaro = (typeof(dj) === 'undefined') ? distance(s1,s2) : dj;
107
+ var p = 0.1; // default scaling factor
108
+ var l = 0 // length of the matching prefix
109
+ while(s1[l] === s2[l] && l < 4) {
110
+ l++;
111
+ }
112
+
113
+ return jaro + l * p * (1 - jaro);
114
+ }
115
115
  }
116
116
  module.exports = JaroWinklerDistance;
117
+
@@ -62,6 +62,7 @@ var NounInflector = function() {
62
62
  this.addIrregular("tooth", "teeth");
63
63
  this.addIrregular("goose", "geese");
64
64
  this.addIrregular("ephemeris", "ephemerides");
65
+ this.addIrregular("cloth", "clothes");
65
66
 
66
67
  // see if it is possible to unify the creation of both the singular and
67
68
  // plural regexes or maybe even just have one list. with a complete list
@@ -32,13 +32,13 @@ function isVowel(c) {
32
32
  function truncate(string, length) {
33
33
  if(string.length >= length)
34
34
  string = string.substring(0, length);
35
-
35
+
36
36
  return string;
37
37
  }
38
38
 
39
39
  function process(token, maxLength) {
40
40
  token = token.toUpperCase();
41
- var primary = '', secondary = '';
41
+ var primary = '', secondary = '';
42
42
  var pos = 0;
43
43
  maxLength == maxLength || 32;
44
44
 
@@ -67,9 +67,9 @@ function process(token, maxLength) {
67
67
 
68
68
  function handleC() {
69
69
 
70
- if(pos >= 1 && !isVowel(token[pos - 2])
70
+ if(pos >= 1 && !isVowel(token[pos - 2])
71
71
  && token[pos - 1] == 'A' && token[pos + 1] == 'H'
72
- && (token[pos + 2] != 'I' && token[pos + 2] != 'I')
72
+ && token[pos + 2] != 'I'
73
73
  || subMatch(-2, 4, ['BACHER', 'MACHER'])) {
74
74
  add('K');
75
75
  pos++;
@@ -85,8 +85,8 @@ function process(token, maxLength) {
85
85
  if(pos > 0 && token.substring(pos + 2, pos + 4) == 'AE') {
86
86
  addSecondary('K', 'X');
87
87
  pos++;
88
- } else if(pos == 0
89
- && (subMatch(1, 6, ['HARAC', 'HARIS'])
88
+ } else if(pos == 0
89
+ && (subMatch(1, 6, ['HARAC', 'HARIS'])
90
90
  || subMatch(1, 4, ['HOR', 'HUM', 'HIA', 'HEM']))
91
91
  && token.substring(pos + 1, pos + 5) != 'HORE') {
92
92
  add('K');
@@ -95,7 +95,7 @@ function process(token, maxLength) {
95
95
  if((subMatchAbsolute(0, 3, ['VAN', 'VON']) || token.substring(0, 3) == 'SCH')
96
96
  || subMatch(-2, 4, ['ORCHES', 'ARCHIT', 'ORCHID'])
97
97
  || subMatch(2, 3, ['T', 'S'])
98
- || ((subMatch(-1, 0, ['A', 'O', 'U', 'E']) || pos == 0)
98
+ || ((subMatch(-1, 0, ['A', 'O', 'U', 'E']) || pos == 0)
99
99
  && subMatch(2, 3, ['B', 'F', 'H', 'L', 'M', 'N', 'R', 'V', 'W']))) {
100
100
  add('K');
101
101
  } else if(pos > 0) {
@@ -103,15 +103,15 @@ function process(token, maxLength) {
103
103
  if(token.substring(0, 2) == 'MC') {
104
104
  add('K');
105
105
  } else {
106
- addSecondary('X', 'K');
106
+ addSecondary('X', 'K');
107
107
  }
108
108
  } else {
109
109
  add('X');
110
110
  }
111
111
 
112
112
  pos++;
113
- }
114
- } else if(token.substring(pos, pos + 2) == 'CZ'
113
+ }
114
+ } else if(token.substring(pos, pos + 2) == 'CZ'
115
115
  && token.substring(pos - 2, pos + 1) != 'WICZ') {
116
116
  addSecondary('S', 'X');
117
117
  pos++;
@@ -119,7 +119,7 @@ function process(token, maxLength) {
119
119
  add('X');
120
120
  pos += 2;
121
121
  } else if(token[pos + 1] == 'C' && pos != 1 && token[0] != 'M') {
122
- if(['I', 'E', 'H'].indexOf(token[pos + 2]) > -1
122
+ if(['I', 'E', 'H'].indexOf(token[pos + 2]) > -1
123
123
  && token.substring(pos + 2, pos + 4) != 'HU') {
124
124
  if(pos == 1 && token[pos - 1] == 'A'
125
125
  || subMatch(-1, 4, ['UCCEE', 'UCCES'])) {
@@ -138,19 +138,19 @@ function process(token, maxLength) {
138
138
  pos++;
139
139
  } else if(['E', 'I', 'Y'].indexOf(token[pos + 1]) > -1) {
140
140
  if(subMatch(1, 3, ['IA', 'IE', 'IO'])) {
141
- addSecondary('S', 'X');
141
+ addSecondary('S', 'X');
142
142
  } else {
143
143
  add('S');
144
144
  }
145
145
  pos++;
146
- } else {
146
+ } else {
147
147
  add('K');
148
148
  if(token[pos + 1] == ' ' && ['C', 'Q', 'G'].indexOf(token[pos + 2])) {
149
149
  pos += 2;
150
150
  } else if(['C', 'K', 'Q'].indexOf(token[pos + 1]) > -1
151
151
  && !subMatch(1, 3, ['CE', 'CI'])) {
152
152
  pos++;
153
- }
153
+ }
154
154
  }
155
155
  }
156
156
 
@@ -165,7 +165,7 @@ function process(token, maxLength) {
165
165
  }
166
166
  } else if(token[pos + 1] == 'T') {
167
167
  add('T');
168
- pos++;
168
+ pos++;
169
169
  } else
170
170
  addCompressedDouble('D', 'T');
171
171
  }
@@ -182,7 +182,7 @@ function process(token, maxLength) {
182
182
  add('K');
183
183
  }
184
184
  pos++;
185
- } else if(pos > 1
185
+ } else if(pos > 1
186
186
  && (['B', 'H', 'D'].indexOf(token[pos - 2]) > -1
187
187
  || ['B', 'H', 'D'].indexOf(token[pos - 3]) > -1
188
188
  || ['B', 'H'].indexOf(token[pos - 4]) > -1)) {
@@ -213,7 +213,7 @@ function process(token, maxLength) {
213
213
  } else if(token.substring(pos + 1, pos + 3) == 'LI' && !slavoGermanic) {
214
214
  addSecondary('KL', 'L');
215
215
  pos++;
216
- } else if(pos == 0 && (token[pos + 1] == 'Y'
216
+ } else if(pos == 0 && (token[pos + 1] == 'Y'
217
217
  || subMatch(1, 3, ['ES', 'EP', 'EB', 'EL', 'EY', 'IB', 'IL', 'IN', 'IE', 'EI', 'ER']))) {
218
218
  addSecondary('K', 'J')
219
219
  } else {
@@ -226,22 +226,22 @@ function process(token, maxLength) {
226
226
  if((pos == 0 || isVowel(token[pos - 1])) && isVowel(token[pos + 1])) {
227
227
  add('H');
228
228
  pos++;
229
- }
230
- }
229
+ }
230
+ }
231
231
 
232
232
  function handleJ() {
233
233
  var jose = (token.substring(pos + 1, pos + 4) == 'OSE');
234
234
 
235
235
  if(san || jose) {
236
- if((pos == 0 && token[pos + 4] == ' ')
236
+ if((pos == 0 && token[pos + 4] == ' ')
237
237
  || san) {
238
- add('H');
238
+ add('H');
239
239
  } else
240
240
  add('J', 'H');
241
241
  } else {
242
242
  if(pos == 0/* && !jose*/) {
243
243
  addSecondary('J', 'A');
244
- } else if(isVowel(token[pos - 1]) && !slavoGermanic
244
+ } else if(isVowel(token[pos - 1]) && !slavoGermanic
245
245
  && (token[pos + 1] == 'A' || token[pos + 1] == 'O')) {
246
246
  addSecondary('J', 'H');
247
247
  } else if(pos == token.length - 1) {
@@ -262,14 +262,14 @@ function process(token, maxLength) {
262
262
  pos++;
263
263
  return;
264
264
  }
265
- pos++;
265
+ pos++;
266
266
  }
267
267
  add('L');
268
268
  }
269
269
 
270
270
  function handleM() {
271
271
  addCompressedDouble('M');
272
- if(token[pos - 1] == 'U' && token[pos + 1] == 'B' &&
272
+ if(token[pos - 1] == 'U' && token[pos + 1] == 'B' &&
273
273
  ((pos == token.length - 2 || token.substring(pos + 2, pos + 4) == 'ER')))
274
274
  pos++;
275
275
  }
@@ -277,10 +277,10 @@ function process(token, maxLength) {
277
277
  function handleP() {
278
278
  if(token[pos + 1] == 'H') {
279
279
  add('F');
280
- pos++;
280
+ pos++;
281
281
  } else {
282
282
  addCompressedDouble('P');
283
-
283
+
284
284
  if(token[pos + 1] == 'B')
285
285
  pos++;
286
286
  }
@@ -292,7 +292,7 @@ function process(token, maxLength) {
292
292
  && !subMatch(-4, -3, ['ME', 'MA'])) {
293
293
  addSecondary('', 'R');
294
294
  } else
295
- addCompressedDouble('R');
295
+ addCompressedDouble('R');
296
296
  }
297
297
 
298
298
  function handleS() {
@@ -312,7 +312,7 @@ function process(token, maxLength) {
312
312
  addSecondary('S', 'X');
313
313
  }
314
314
  pos++;
315
- } else if((pos == 0 && ['M', 'N', 'L', 'W'].indexOf(token[pos + 1]) > -1)
315
+ } else if((pos == 0 && ['M', 'N', 'L', 'W'].indexOf(token[pos + 1]) > -1)
316
316
  || token[pos + 1] == 'Z') {
317
317
  addSecondary('S', 'X');
318
318
  if(token[pos + 1] == 'Z')
@@ -326,18 +326,18 @@ function process(token, maxLength) {
326
326
  } else if(pos == 0 && !isVowel(token[3]) && token[3] != 'W') {
327
327
  addSecondary('X', 'S');
328
328
  } else {
329
- add('X');
330
- }
329
+ add('X');
330
+ }
331
331
  } else if(['I', 'E', 'Y'].indexOf(token[pos + 2]) > -1) {
332
332
  add('S');
333
333
  } else {
334
334
  add('SK');
335
335
  }
336
336
 
337
- pos += 2;
337
+ pos += 2;
338
338
  } else if(pos == token.length - 1
339
339
  && subMatch(-2, 0, ['AI', 'OI'])) {
340
- addSecondary('', 'S');
340
+ addSecondary('', 'S');
341
341
  } else if(token[pos + 1] != 'L' && (
342
342
  token[pos - 1] != 'A' && token[pos - 1] != 'I')) {
343
343
  addCompressedDouble('S');
@@ -353,12 +353,12 @@ function process(token, maxLength) {
353
353
  } else if(subMatch(1, 3, ['IA', 'CH'])) {
354
354
  add('X');
355
355
  pos += 2;
356
- } else if(token[pos + 1] == 'H'
356
+ } else if(token[pos + 1] == 'H'
357
357
  || token.substring(1, 2) == 'TH') {
358
- if(subMatch(2, 4, ['OM', 'AM'])
358
+ if(subMatch(2, 4, ['OM', 'AM'])
359
359
  || ['VAN ', 'VON '].indexOf(token.substring(0, 4)) > -1
360
360
  || token.substring(0, 3) == 'SCH') {
361
- add('T');
361
+ add('T');
362
362
  } else
363
363
  addSecondary('0', 'T');
364
364
  pos++;
@@ -373,7 +373,7 @@ function process(token, maxLength) {
373
373
  function handleX() {
374
374
  if(pos == 0) {
375
375
  add('S');
376
- } else if(!(pos == token.length - 1
376
+ } else if(!(pos == token.length - 1
377
377
  && (['IAU', 'EAU', 'IEU'].indexOf(token.substring(pos - 3, pos)) > -1
378
378
  || ['AU', 'OU'].indexOf(token.substring(pos - 2, pos)) > -1))) {
379
379
  add('KS');
@@ -387,7 +387,7 @@ function process(token, maxLength) {
387
387
  } else if (isVowel(token[1])) {
388
388
  addSecondary('A', 'F');
389
389
  }
390
- } else if((pos == token.length - 1 && isVowel(token[pos - 1])
390
+ } else if((pos == token.length - 1 && isVowel(token[pos - 1])
391
391
  || subMatch(-1, 4, ['EWSKI', 'EWSKY', 'OWSKI', 'OWSKY'])
392
392
  || token.substring(0, 3) == 'SCH')) {
393
393
  addSecondary('', 'F');
@@ -401,11 +401,11 @@ function process(token, maxLength) {
401
401
  function handleZ() {
402
402
  if(token[pos + 1] == 'H') {
403
403
  add('J');
404
- pos++;
405
- } else if(subMatch(1, 3, ['ZO', 'ZI', 'ZA'])
404
+ pos++;
405
+ } else if(subMatch(1, 3, ['ZO', 'ZI', 'ZA'])
406
406
  || (slavoGermanic && pos > 0 && token[pos - 1] != 'T')) {
407
407
  addSecondary('S', 'TS');
408
- pos++;
408
+ pos++;
409
409
  } else
410
410
  addCompressedDouble('Z', 'S');
411
411
  }
@@ -421,7 +421,7 @@ function process(token, maxLength) {
421
421
  while(pos < token.length) {
422
422
 
423
423
  switch(token[pos]) {
424
- case 'A': case 'E': case 'I': case 'O': case 'U': case 'Y':
424
+ case 'A': case 'E': case 'I': case 'O': case 'U': case 'Y':
425
425
  case 'Ê': case 'É': case 'É': case'À':
426
426
  if(pos == 0)
427
427
  add('A');
@@ -502,7 +502,7 @@ function compare(stringA, stringB) {
502
502
  var encodingsA = process(stringA),
503
503
  encodingsB = process(stringB);
504
504
 
505
- return encodingsA[0] == encodingsB[0] ||
505
+ return encodingsA[0] == encodingsB[0] ||
506
506
  encodingsA[1] == encodingsB[1];
507
507
  };
508
508
 
@@ -0,0 +1 @@
1
+ {"classifier":{"examples":{"computing":[[1,1,0,0,0,0,0,0,0],[0,0,1,1,0,0,0,0,0],[0,0,0,1,1,1,0,0,0]],"literature":[[0,0,1,0,0,0,1,0,0],[0,0,0,0,0,0,1,1,0],[0,0,0,0,0,0,1,0,1]]},"features":[],"featurePositions":{},"maxFeaturePosition":0,"classifications":["computing","literature"],"exampleCount":6,"theta":[{"elements":[2.0057203693422903,2.0057203693422903,0.0904766662626176,3.447364098636818,0.9696521506748157,0.9696521506748157,-4.840706080886848,-1.2267353995937322,-1.2267353995937322]},{"elements":[-2.0057203693422894,-2.0057203693422894,-0.09047666626261801,-3.4473640986368186,-0.9696521506748156,-0.9696521506748156,4.84070608088685,1.2267353995937313,1.2267353995937313]}]},"docs":[{"label":"computing","text":["fix","box"]},{"label":"computing","text":["write","code"]},{"label":"computing","text":["nasti","script","code"]},{"label":"literature","text":["write","book"]},{"label":"literature","text":["read","book"]},{"label":"literature","text":["studi","book"]}],"features":{"fix":1,"box":1,"write":2,"code":2,"nasti":1,"script":1,"book":3,"read":1,"studi":1},"stemmer":{},"lastAdded":6,"events":{"domain":null,"_events":{},"_eventsCount":0}}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "natural",
3
3
  "description": "General natural language (tokenizing, stemming (English, Russian, Spanish), classification, inflection, phonetics, tfidf, WordNet, jaro-winkler, Levenshtein distance, Dice's Coefficient) facilities for node.",
4
- "version": "0.5.0",
4
+ "version": "0.5.1",
5
5
  "homepage": "https://github.com/NaturalNode/natural",
6
6
  "repository": {
7
7
  "type": "git",
@@ -13,11 +13,13 @@
13
13
  "dependencies": {
14
14
  "apparatus": ">= 0.0.9",
15
15
  "sylvester": ">= 0.0.12",
16
- "underscore": ">=1.3.1"
16
+ "underscore": ">=1.3.1",
17
+ "webworker-threads": ">=0.6.2"
17
18
  },
18
19
  "devDependencies": {
19
- "jasmine-node": "~1.13.1",
20
- "uubench": "0.0.x"
20
+ "uubench": "0.0.x",
21
+ "sinon": "^1.12.2",
22
+ "jasmine-node": "~1.13.1"
21
23
  },
22
24
  "scripts": {
23
25
  "test": "NODE_PATH=. node_modules/jasmine-node/bin/jasmine-node spec/"