natural 0.1.28 → 0.4.0

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 (117) hide show
  1. package/.editorconfig +7 -0
  2. package/.npmignore +4 -11
  3. package/README.md +190 -50
  4. package/bayes_classifier.json +1 -0
  5. package/lib/natural/analyzers/sentence_analyzer.js +8 -5
  6. package/lib/natural/brill_pos_tagger/data/Dutch/README.txt +2 -0
  7. package/lib/natural/brill_pos_tagger/data/Dutch/brill_CONTEXTRULES.jg +285 -0
  8. package/lib/natural/brill_pos_tagger/data/Dutch/brill_LEXICON.jg +11699 -0
  9. package/lib/natural/brill_pos_tagger/data/English/README.txt +8 -0
  10. package/lib/natural/brill_pos_tagger/data/English/lexicon_from_posjs.json +297827 -0
  11. package/lib/natural/brill_pos_tagger/data/English/tr_from_brill_paper.txt +10 -0
  12. package/lib/natural/brill_pos_tagger/data/English/tr_from_posjs.txt +18 -0
  13. package/lib/natural/brill_pos_tagger/lib/Brill_POS_Tagger.js +136 -0
  14. package/lib/natural/brill_pos_tagger/lib/Predicate.js +410 -0
  15. package/lib/natural/brill_pos_tagger/lib/TF_Parser.js +717 -0
  16. package/lib/natural/brill_pos_tagger/lib/TF_Parser.pegjs +55 -0
  17. package/lib/natural/brill_pos_tagger/lib/TransformationRule.js +49 -0
  18. package/lib/natural/classifiers/bayes_classifier.js +6 -5
  19. package/lib/natural/classifiers/classifier.js +3 -2
  20. package/lib/natural/classifiers/logistic_regression_classifier.js +9 -5
  21. package/lib/natural/distance/dice_coefficient.js +22 -12
  22. package/lib/natural/distance/jaro-winkler_distance.js +13 -8
  23. package/lib/natural/index.js +6 -0
  24. package/lib/natural/inflectors/present_verb_inflector.js +1 -0
  25. package/lib/natural/ngrams/ngrams.js +6 -0
  26. package/lib/natural/normalizers/normalizer_ja.js +26 -8
  27. package/lib/natural/phonetics/double_metaphone.js +9 -4
  28. package/lib/natural/phonetics/metaphone.js +4 -0
  29. package/lib/natural/phonetics/soundex.js +11 -7
  30. package/lib/natural/spellcheck/spellcheck.js +80 -0
  31. package/lib/natural/stemmers/porter_stemmer.js +90 -62
  32. package/lib/natural/stemmers/porter_stemmer_fr.js +8 -3
  33. package/lib/natural/stemmers/porter_stemmer_pt.js +302 -0
  34. package/lib/natural/stemmers/stemmer.js +31 -6
  35. package/lib/natural/stemmers/stemmer_pt.js +65 -0
  36. package/lib/natural/stemmers/token.js +163 -0
  37. package/lib/natural/tfidf/tfidf.js +49 -18
  38. package/lib/natural/tokenizers/regexp_tokenizer.js +3 -3
  39. package/{examples/inflection/noun.js → lib/natural/tokenizers/sentence_tokenizer.js} +14 -10
  40. package/lib/natural/tokenizers/tokenizer_case.js +58 -0
  41. package/lib/natural/util/bag.js +1 -0
  42. package/lib/natural/util/stopwords_pt.js +136 -0
  43. package/lib/natural/wordnet/wordnet.js +2 -2
  44. package/lib/natural/wordnet/wordnet_file.js +1 -1
  45. package/lr_classifier.json +1 -0
  46. package/package.json +7 -4
  47. package/package.json~ +66 -0
  48. package/benchmarks/index.js +0 -32
  49. package/benchmarks/metaphone.js +0 -27
  50. package/benchmarks/soundex.js +0 -27
  51. package/examples/classification/basic.js +0 -35
  52. package/examples/classification/recall.js +0 -28
  53. package/examples/classification/store.js +0 -37
  54. package/examples/inflection/count.js +0 -35
  55. package/examples/inflection/noun_attach.js +0 -33
  56. package/examples/phonetics/compare.js +0 -39
  57. package/examples/phonetics/sounds_like.js +0 -40
  58. package/examples/phonetics/tokenize_and_phoneticize.js +0 -54
  59. package/examples/stemming/stem_corpus.js +0 -29
  60. package/examples/stemming/stem_word.js +0 -26
  61. package/examples/tfidf/array_example.js +0 -34
  62. package/examples/tfidf/multiple_terms.js +0 -33
  63. package/examples/tfidf/tfidf_example.js +0 -44
  64. package/io_spec/test_data/tfidf/one +0 -1
  65. package/io_spec/test_data/tfidf/two +0 -1
  66. package/io_spec/tfidf_spec.js +0 -50
  67. package/io_spec/wordnet_spec.js +0 -81
  68. package/spec/aggressive_tokenizer_es_spec.js +0 -36
  69. package/spec/aggressive_tokenizer_fr_spec.js +0 -101
  70. package/spec/aggressive_tokenizer_nl_spec.js +0 -36
  71. package/spec/aggressive_tokenizer_pt_spec.js +0 -56
  72. package/spec/aggressive_tokenizer_spec.js +0 -56
  73. package/spec/bayes_classifier_spec.js +0 -173
  74. package/spec/classifier_spec.js +0 -91
  75. package/spec/count_inflector_fr_spec.js +0 -44
  76. package/spec/count_inflector_spec.js +0 -73
  77. package/spec/dice_coefficient_spec.js +0 -26
  78. package/spec/double_metaphone_spec.js +0 -743
  79. package/spec/jaro-winkler_spec.js +0 -53
  80. package/spec/lancaster_stemmer_spec.js +0 -73
  81. package/spec/levenshtein_spec.js +0 -66
  82. package/spec/logistic_regression_classifier_spec.js +0 -122
  83. package/spec/longest_path_tree_spec.js +0 -102
  84. package/spec/metaphone_spec.js +0 -247
  85. package/spec/ngram_spec.js +0 -130
  86. package/spec/ngram_zh_spec.js +0 -122
  87. package/spec/normalizer_ja_spec.js +0 -154
  88. package/spec/normalizer_no_spec.js +0 -111
  89. package/spec/normalizer_spec.js +0 -118
  90. package/spec/noun_inflector_fr_spec.js +0 -476
  91. package/spec/noun_inflector_ja_spec.js +0 -112
  92. package/spec/noun_inflector_spec.js +0 -270
  93. package/spec/porter_stemmer_fr_spec.js +0 -101
  94. package/spec/porter_stemmer_it_spec.js +0 -44
  95. package/spec/porter_stemmer_no_spec.js +0 -143
  96. package/spec/porter_stemmer_ru_spec.js +0 -63
  97. package/spec/porter_stemmer_spec.js +0 -137
  98. package/spec/present_verb_inflector_spec.js +0 -125
  99. package/spec/sentence_analyzer_spec.js +0 -29
  100. package/spec/shortest_path_tree_spec.js +0 -132
  101. package/spec/soundex_spec.js +0 -154
  102. package/spec/stemmer_ja_spec.js +0 -66
  103. package/spec/test_data/snowball_fr.txt +0 -20402
  104. package/spec/test_data/snowball_it.txt +0 -35482
  105. package/spec/test_data/snowball_no.txt +0 -20628
  106. package/spec/test_data/tfdif_testcorpus.txt +0 -7
  107. package/spec/test_data/tfidf_document1.txt +0 -1
  108. package/spec/test_data/tfidf_document2.txt +0 -1
  109. package/spec/test_data/tfidf_document3.txt +0 -1
  110. package/spec/test_data/tfidf_document4.txt +0 -1
  111. package/spec/test_data/tfidf_teststopwords.txt +0 -1
  112. package/spec/test_data/wordnet_mini.sql +0 -95
  113. package/spec/tfidf_spec.js +0 -198
  114. package/spec/tokenizer_ja_spec.js +0 -58
  115. package/spec/transliterator_ja_spec.js +0 -103
  116. package/spec/treebank_word_tokenizer_spec.js +0 -36
  117. package/spec/trie_spec.js +0 -206
@@ -0,0 +1,55 @@
1
+ {
2
+ var TransformationRule = require("./TransformationRule");
3
+ }
4
+
5
+ transformation_rules = rules: (S transformation_rule S) +
6
+ {
7
+ var result = [];
8
+
9
+ for (var i = 0; i < rules.length; i++) {
10
+ result.push(rules[i][1]);
11
+ }
12
+ return(result);
13
+ }
14
+
15
+ transformation_rule = c1: category1 c2: category2 pred: predicate par1: parameter par2: parameter ?
16
+ {
17
+ // Construct rule
18
+ var result = new TransformationRule(c1, c2, pred, par1, par2);
19
+ return(result);
20
+ }
21
+
22
+ category1 = wild_card / identifier
23
+
24
+ category2 = identifier
25
+
26
+ predicate = identifier
27
+
28
+ parameter = identifier
29
+
30
+ identifier =
31
+ characters: [a-zA-Z_0-9_\-\.,()]+ S_no_eol
32
+ {
33
+ var s = "";
34
+ for (var i = 0; i < characters.length; i++) {
35
+ s += characters[i];
36
+ }
37
+ return(s);
38
+ }
39
+
40
+ wild_card = wc: "*" S_no_eol
41
+ {
42
+ return(wc)
43
+ }
44
+
45
+ // Blanks
46
+ EOL =
47
+ '\r\n' / '\n' / '\r'
48
+ Comment =
49
+ "\/\/" (!EOL .)* (EOL/EOI)
50
+ S =
51
+ (' ' / '\t' / EOL / Comment)*
52
+ S_no_eol =
53
+ (' ' / '\t' / Comment)*
54
+ EOI=
55
+ !.
@@ -0,0 +1,49 @@
1
+ /*
2
+ Transformation rules for the Brill tagger
3
+ Copyright (C) 2015 Hugo W.L. ter Doest
4
+
5
+ This program is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU General Public License as published by
7
+ the Free Software Foundation, either version 3 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU General Public License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License
16
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ */
18
+
19
+ var log4js = require('log4js');
20
+ var logger = log4js.getLogger();
21
+
22
+ var Predicate = require("./Predicate");
23
+
24
+ logger.setLevel('ERROR');
25
+
26
+ var category_wild_card = "*";
27
+
28
+ function TransformationRule(c1, c2, predicate, parameter1, parameter2) {
29
+ this.literal = [c1, c2, predicate, parameter1, parameter2];
30
+ this.predicate = new Predicate(predicate, parameter1, parameter2);
31
+ this.old_category = c1;
32
+ this.new_category = c2;
33
+ logger.debug('TransformationRule constructor: ' + this.literal);
34
+ }
35
+
36
+ TransformationRule.prototype.apply = function(tagged_sentence, position) {
37
+ if ((tagged_sentence[position][1] === this.old_category) ||
38
+ (this.old_category === category_wild_card)) {
39
+ if (this.predicate.evaluate(tagged_sentence, position)) {
40
+ tagged_sentence[position][1] = this.new_category;
41
+ logger.debug('TransformationRule.apply: changed category ' +
42
+ this.old_category + ' to ' + this.new_category +
43
+ ' at position ' + position);
44
+ }
45
+ }
46
+ };
47
+
48
+ module.exports = TransformationRule;
49
+
@@ -20,10 +20,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
20
  THE SOFTWARE.
21
21
  */
22
22
 
23
- var PorterStemmer = require('../stemmers/porter_stemmer'),
24
- util = require('util'),
25
- Classifier = require('./classifier'),
26
- ApparatusBayesClassifier = require('apparatus').BayesClassifier;
23
+ var util = require('util'),
24
+ Classifier = require('./classifier'),
25
+ ApparatusBayesClassifier = require('apparatus').BayesClassifier;
27
26
 
28
27
  var BayesClassifier = function(stemmer, smoothing) {
29
28
  var abc = new ApparatusBayesClassifier();
@@ -48,7 +47,9 @@ function load(filename, stemmer, callback) {
48
47
  if (err) {
49
48
  callback(err);
50
49
  }
51
- callback(err, restore(classifier, stemmer));
50
+ else {
51
+ callback(err, restore(classifier, stemmer));
52
+ }
52
53
  });
53
54
  }
54
55
 
@@ -47,8 +47,9 @@ function addDocument(text, classification) {
47
47
  text: text
48
48
  });
49
49
 
50
- for(var i = 0; i < text.length; i++) {
51
- this.features[text[i]] = 1;
50
+ for (var i = 0; i < text.length; i++) {
51
+ var token = text[i];
52
+ this.features[token] = (this.features[token] || 0) + 1;
52
53
  }
53
54
  }
54
55
 
@@ -20,10 +20,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
20
  THE SOFTWARE.
21
21
  */
22
22
 
23
- var PorterStemmer = require('../stemmers/porter_stemmer'),
24
- util = require('util'),
25
- Classifier = require('./classifier'),
26
- ApparatusLogisticRegressionClassifier = require('apparatus').LogisticRegressionClassifier;
23
+ var util = require('util'),
24
+ Classifier = require('./classifier'),
25
+ ApparatusLogisticRegressionClassifier = require('apparatus').LogisticRegressionClassifier;
27
26
 
28
27
  var LogisticRegressionClassifier = function(stemmer) {
29
28
  Classifier.call(this, new ApparatusLogisticRegressionClassifier(), stemmer);
@@ -41,7 +40,12 @@ function restore(classifier, stemmer) {
41
40
 
42
41
  function load(filename, stemmer, callback) {
43
42
  Classifier.load(filename, function(err, classifier) {
44
- callback(err, restore(classifier, stemmer));
43
+ if (err) {
44
+ callback(err);
45
+ }
46
+ else {
47
+ callback(err, restore(classifier, stemmer));
48
+ }
45
49
  });
46
50
  }
47
51
 
@@ -48,22 +48,32 @@ var sanitize = function (str) {
48
48
 
49
49
  // Compare two strings, and spit out a number from 0-1
50
50
  var compare = function (str1, str2) {
51
- var pairs1 = wordLetterPairs(sanitize(str1));
52
- var pairs2 = wordLetterPairs(sanitize(str2));
51
+ var sanitized_str1 = sanitize(str1);
52
+ var sanitized_str2 = sanitize(str2);
53
+ var pairs1 = wordLetterPairs(sanitized_str1);
54
+ var pairs2 = wordLetterPairs(sanitized_str2);
53
55
  var intersection = 0, union = pairs1.length + pairs2.length;
54
- var i, j, pair1, pair2;
55
- for (i = 0; i < pairs1.length; i++) {
56
- pair1 = pairs1[i];
57
- for (j = 0; j < pairs2.length; j++) {
58
- pair2 = pairs2[j];
59
- if (pair1 == pair2) {
60
- intersection ++;
61
- delete pairs2[j];
62
- break;
56
+ if (union === 0) {
57
+ if (sanitized_str1 === sanitized_str2) {
58
+ return 1;
59
+ } else {
60
+ return 0;
61
+ }
62
+ } else {
63
+ var i, j, pair1, pair2;
64
+ for (i = 0; i < pairs1.length; i++) {
65
+ pair1 = pairs1[i];
66
+ for (j = 0; j < pairs2.length; j++) {
67
+ pair2 = pairs2[j];
68
+ if (pair1 == pair2) {
69
+ intersection ++;
70
+ delete pairs2[j];
71
+ break;
72
+ }
63
73
  }
64
74
  }
75
+ return 2 * intersection / union;
65
76
  }
66
- return 2 * intersection / union;
67
77
  };
68
78
 
69
79
  module.exports = compare;
@@ -99,13 +99,18 @@ function distance(s1, s2) {
99
99
  // s2 is the second string to compare
100
100
  // dj is the Jaro Distance (if you've already computed it), leave blank and the method handles it
101
101
  function JaroWinklerDistance(s1, s2, dj) {
102
- var jaro;
103
- (typeof(dj) == 'undefined')? jaro = distance(s1,s2) : jaro = dj;
104
- var p = 0.1; //
105
- var l = 0 // length of the matching prefix
106
- while(s1[l] == s2[l] && l < 4)
107
- l++;
108
-
109
- return jaro + l * p * (1 - jaro);
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
+ }
110
115
  }
111
116
  module.exports = JaroWinklerDistance;
@@ -31,12 +31,14 @@ exports.PorterStemmerRu = require('./stemmers/porter_stemmer_ru');
31
31
  exports.PorterStemmerEs = require('./stemmers/porter_stemmer_es');
32
32
  exports.PorterStemmerIt = require('./stemmers/porter_stemmer_it');
33
33
  exports.PorterStemmerNo = require('./stemmers/porter_stemmer_no');
34
+ exports.PorterStemmerPt = require('./stemmers/porter_stemmer_pt');
34
35
  exports.LancasterStemmer = require('./stemmers/lancaster_stemmer');
35
36
  exports.StemmerFr = require('./stemmers/stemmer_fr');
36
37
  exports.StemmerPl = require('./stemmers/stemmer_pl');
37
38
  exports.StemmerJa = require('./stemmers/stemmer_ja');
38
39
  exports.AggressiveTokenizerNl = require('./tokenizers/aggressive_tokenizer_nl');
39
40
  exports.AggressiveTokenizerFa = require('./tokenizers/aggressive_tokenizer_fa');
41
+ exports.AggressiveTokenizerFr = require('./tokenizers/aggressive_tokenizer_fr');
40
42
  exports.AggressiveTokenizerRu = require('./tokenizers/aggressive_tokenizer_ru');
41
43
  exports.AggressiveTokenizerEs = require('./tokenizers/aggressive_tokenizer_es');
42
44
  exports.AggressiveTokenizerIt = require('./tokenizers/aggressive_tokenizer_it');
@@ -44,11 +46,13 @@ exports.AggressiveTokenizerPl = require('./tokenizers/aggressive_tokenizer_pl');
44
46
  exports.AggressiveTokenizerPt = require('./tokenizers/aggressive_tokenizer_pt');
45
47
  exports.AggressiveTokenizerNo = require('./tokenizers/aggressive_tokenizer_no');
46
48
  exports.AggressiveTokenizer = require('./tokenizers/aggressive_tokenizer');
49
+ exports.CaseTokenizer = require('./tokenizers/tokenizer_case');
47
50
  exports.RegexpTokenizer = require('./tokenizers/regexp_tokenizer').RegexpTokenizer;
48
51
  exports.WordTokenizer = require('./tokenizers/regexp_tokenizer').WordTokenizer;
49
52
  exports.WordPunctTokenizer = require('./tokenizers/regexp_tokenizer').WordPunctTokenizer;
50
53
  exports.TreebankWordTokenizer = require('./tokenizers/treebank_word_tokenizer');
51
54
  exports.TokenizerJa = require('./tokenizers/tokenizer_ja');
55
+ exports.SentenceTokenizer = require('./tokenizers/sentence_tokenizer');
52
56
  exports.BayesClassifier = require('./classifiers/bayes_classifier');
53
57
  exports.LogisticRegressionClassifier = require('./classifiers/logistic_regression_classifier');
54
58
  exports.NounInflector = require('./inflectors/noun_inflector');
@@ -62,6 +66,7 @@ exports.Trie = require('./trie/trie');
62
66
  exports.SentenceAnalyzer = require('./analyzers/sentence_analyzer');
63
67
  exports.stopwords = require('./util/stopwords').words;
64
68
  exports.ShortestPathTree = require('./util/shortest_path_tree');
69
+ exports.Spellcheck = require('./spellcheck/spellcheck');
65
70
  exports.LongestPathTree = require('./util/longest_path_tree');
66
71
  exports.EdgeWeightedDigraph = require('./util/edge_weighted_digraph');
67
72
  exports.NGrams = require('./ngrams/ngrams');
@@ -73,3 +78,4 @@ exports.normalize = require('./normalizers/normalizer').normalize_tokens;
73
78
  exports.normalize_ja = require('./normalizers/normalizer_ja').normalize_ja;
74
79
  exports.removeDiacritics = require('./normalizers/remove_diacritics');
75
80
  exports.transliterate_ja = require('./transliterators/ja');
81
+ exports.BrillPOSTagger = require('./brill_pos_tagger/lib/Brill_POS_Tagger');
@@ -51,6 +51,7 @@ var VerbInflector = function() {
51
51
  this.addIrregular("am", "are");
52
52
  this.addIrregular("is", "are");
53
53
  this.addIrregular("was", "were");
54
+ this.addIrregular("has", "have");
54
55
 
55
56
  this.singularForms.regularForms.push([/ed$/i, 'ed']);
56
57
  this.singularForms.regularForms.push([/ss$/i, 'sses']);
@@ -25,6 +25,8 @@ var _ = require("underscore")._,
25
25
  tokenizer = new Tokenizer();
26
26
 
27
27
  exports.setTokenizer = function(t) {
28
+ if(!_.isFunction(t.tokenize))
29
+ throw new Error('Expected a valid Tokenizer');
28
30
  tokenizer = t;
29
31
  }
30
32
 
@@ -40,6 +42,10 @@ exports.trigrams = function(sequence, startSymbol, endSymbol) {
40
42
  return ngrams(sequence, 3, startSymbol, endSymbol);
41
43
  }
42
44
 
45
+ exports.multrigrams = function(sequence, n, startSymbol, endSymbol) {
46
+ return ngrams(sequence, n, startSymbol, endSymbol);
47
+ }
48
+
43
49
  var ngrams = function(sequence, n, startSymbol, endSymbol) {
44
50
  var result = [];
45
51
 
@@ -120,26 +120,21 @@ var conversionTables = {
120
120
  '9': '9'
121
121
  },
122
122
 
123
- punctuation: {
123
+ symbol: {
124
124
  '_': '_',
125
125
  '-': '-',
126
- '・': '・',
127
126
  ',': ',',
128
- '、': '、',
129
127
  ';': ';',
130
128
  ':': ':',
131
129
  '!': '!',
132
130
  '?': '?',
133
131
  '.': '.',
134
- '。': '。',
135
132
  '(': '(',
136
133
  ')': ')',
137
134
  '[': '[',
138
135
  ']': ']',
139
136
  '{': '{',
140
137
  '}': '}',
141
- '「': '「',
142
- '」': '」',
143
138
  '@': '@',
144
139
  '*': '*',
145
140
  '\': '\\',
@@ -154,7 +149,7 @@ var conversionTables = {
154
149
  '=': '=',
155
150
  '>': '>',
156
151
  '|': '|',
157
- '~': '~',
152
+ // Never converted: '~': '~',
158
153
  '≪': '«',
159
154
  '≫': '»',
160
155
  '─': '-',
@@ -162,6 +157,16 @@ var conversionTables = {
162
157
  '"': '"'
163
158
  },
164
159
 
160
+ purePunctuation: {
161
+ '、': '、',
162
+ '。': '。',
163
+ '・': '・',
164
+ '「': '「',
165
+ '」': '」'
166
+ },
167
+
168
+ punctuation: {},
169
+
165
170
  katakana: {
166
171
  '゛': '゙',
167
172
  '゜': '゚',
@@ -492,9 +497,17 @@ var fixCompositeSymbolsTable = {
492
497
  '㍗': 'ワット'
493
498
  };
494
499
 
500
+ // punctuation is pure_punctuation
501
+ conversionTables.fullwidthToHalfwidth.punctuation = merge(
502
+ conversionTables.fullwidthToHalfwidth.symbol,
503
+ conversionTables.fullwidthToHalfwidth.purePunctuation
504
+ )
505
+
495
506
  // Fill in the conversion tables with the flipped tables.
496
507
  conversionTables.halfwidthToFullwidth.alphabet = flip(conversionTables.fullwidthToHalfwidth.alphabet);
497
508
  conversionTables.halfwidthToFullwidth.numbers = flip(conversionTables.fullwidthToHalfwidth.numbers);
509
+ conversionTables.halfwidthToFullwidth.symbol = flip(conversionTables.fullwidthToHalfwidth.symbol);
510
+ conversionTables.halfwidthToFullwidth.purePunctuation = flip(conversionTables.fullwidthToHalfwidth.purePunctuation);
498
511
  conversionTables.halfwidthToFullwidth.punctuation = flip(conversionTables.fullwidthToHalfwidth.punctuation);
499
512
  conversionTables.halfwidthToFullwidth.katakana = flip(conversionTables.fullwidthToHalfwidth.katakana);
500
513
 
@@ -502,7 +515,8 @@ conversionTables.halfwidthToFullwidth.katakana = flip(conversionTables.fullwidth
502
515
  conversionTables.normalize = merge(
503
516
  conversionTables.fullwidthToHalfwidth.alphabet,
504
517
  conversionTables.fullwidthToHalfwidth.numbers,
505
- conversionTables.halfwidthToFullwidth.punctuation,
518
+ conversionTables.fullwidthToHalfwidth.symbol,
519
+ conversionTables.halfwidthToFullwidth.purePunctuation,
506
520
  conversionTables.halfwidthToFullwidth.katakana
507
521
  );
508
522
 
@@ -510,6 +524,8 @@ var converters = {
510
524
  fullwidthToHalfwidth: {
511
525
  alphabet: replacer(conversionTables.fullwidthToHalfwidth.alphabet),
512
526
  numbers: replacer(conversionTables.fullwidthToHalfwidth.numbers),
527
+ symbol: replacer(conversionTables.fullwidthToHalfwidth.symbol),
528
+ purePunctuation: replacer(conversionTables.fullwidthToHalfwidth.purePunctuation),
513
529
  punctuation: replacer(conversionTables.fullwidthToHalfwidth.punctuation),
514
530
  katakana: replacer(conversionTables.fullwidthToHalfwidth.katakana)
515
531
  },
@@ -517,6 +533,8 @@ var converters = {
517
533
  halfwidthToFullwidth: {
518
534
  alphabet: replacer(conversionTables.halfwidthToFullwidth.alphabet),
519
535
  numbers: replacer(conversionTables.halfwidthToFullwidth.numbers),
536
+ symbol: replacer(conversionTables.halfwidthToFullwidth.symbol),
537
+ purePunctuation: replacer(conversionTables.halfwidthToFullwidth.purePunctuation),
520
538
  punctuation: replacer(conversionTables.halfwidthToFullwidth.punctuation),
521
539
  katakana: replacer(conversionTables.halfwidthToFullwidth.katakana)
522
540
  },
@@ -66,7 +66,8 @@ function process(token, maxLength) {
66
66
  }
67
67
 
68
68
  function handleC() {
69
- if(pos > 1 && !isVowel(token[pos - 2])
69
+
70
+ if(pos >= 1 && !isVowel(token[pos - 2])
70
71
  && token[pos - 1] == 'A' && token[pos + 1] == 'H'
71
72
  && (token[pos + 2] != 'I' && token[pos + 2] != 'I')
72
73
  || subMatch(-2, 4, ['BACHER', 'MACHER'])) {
@@ -74,7 +75,9 @@ function process(token, maxLength) {
74
75
  pos++;
75
76
  } else if(pos == 0 && token.substring(1, 6) == 'EASAR') {
76
77
  add('S');
77
- pos++;
78
+ add('S');
79
+ add('R');
80
+ pos += 6;
78
81
  } else if(token.substring(pos + 1, pos + 4) == 'HIA') {
79
82
  add('K');
80
83
  pos++;
@@ -84,7 +87,7 @@ function process(token, maxLength) {
84
87
  pos++;
85
88
  } else if(pos == 0
86
89
  && (subMatch(1, 6, ['HARAC', 'HARIS'])
87
- || subMatch(1, 3, ['HOR', 'HUM', 'HIA', 'HEM']))
90
+ || subMatch(1, 4, ['HOR', 'HUM', 'HIA', 'HEM']))
88
91
  && token.substring(pos + 1, pos + 5) != 'HORE') {
89
92
  add('K');
90
93
  pos++;
@@ -96,6 +99,7 @@ function process(token, maxLength) {
96
99
  && subMatch(2, 3, ['B', 'F', 'H', 'L', 'M', 'N', 'R', 'V', 'W']))) {
97
100
  add('K');
98
101
  } else if(pos > 0) {
102
+
99
103
  if(token.substring(0, 2) == 'MC') {
100
104
  add('K');
101
105
  } else {
@@ -415,6 +419,7 @@ function process(token, maxLength) {
415
419
  }
416
420
 
417
421
  while(pos < token.length) {
422
+
418
423
  switch(token[pos]) {
419
424
  case 'A': case 'E': case 'I': case 'O': case 'U': case 'Y':
420
425
  case 'Ê': case 'É': case 'É': case'À':
@@ -488,7 +493,7 @@ function process(token, maxLength) {
488
493
  }
489
494
 
490
495
  pos++;
491
- }
496
+ }
492
497
 
493
498
  return [truncate(primary, maxLength), truncate(secondary, maxLength)];
494
499
  }
@@ -38,7 +38,11 @@ function dropBafterMAtEnd(token) {
38
38
  }
39
39
 
40
40
  function cTransform(token) {
41
+
42
+
41
43
  token = token.replace(/([^s]|^)(c)(h)/g, '$1x$3').trim();
44
+
45
+
42
46
  token = token.replace(/cia/g, 'xia');
43
47
  token = token.replace(/c(i|e|y)/g, 's$1');
44
48
  token = token.replace(/c/g, 'k');
@@ -47,7 +47,7 @@ function transformR(token) {
47
47
  }
48
48
 
49
49
  function condense(token) {
50
- return token.replace(/(\d)[hw]?\1+/g, '$1').replace(/[hw]/g, '');
50
+ return token.replace(/(\d)?\1+/g, '$1');
51
51
  }
52
52
 
53
53
  function padRight0(token) {
@@ -57,16 +57,20 @@ function padRight0(token) {
57
57
  return token;
58
58
  }
59
59
 
60
+ function transform(token) {
61
+ return transformLipps(transformThroats(
62
+ transformToungue(transformL(transformHum(transformR(token))))));
63
+ }
64
+
60
65
  var SoundEx = new Phonetic();
61
66
  module.exports = SoundEx;
62
67
 
63
68
  SoundEx.process = function(token, maxLength) {
64
- token = token.toLowerCase();
65
-
66
- return token.charAt(0).toUpperCase() + padRight0(condense(transformLipps(transformThroats(
67
- transformToungue(transformL(transformHum(transformR(
68
- token.substr(1, token.length - 1).replace(/[aeiouy]/g, '')))))))
69
- )).substr(0, (maxLength && maxLength - 1) || 3);
69
+ token = token.toLowerCase();
70
+ var transformed = condense(transform(token.substr(1, token.length - 1))); // anything that isn't a digit goes
71
+ // deal with duplicate INITIAL consonant SOUNDS
72
+ transformed = transformed.replace(new RegExp("^" + transform(token.charAt(0))), '');
73
+ return token.charAt(0).toUpperCase() + padRight0(transformed.replace(/\D/g, '')).substr(0, (maxLength && maxLength - 1) || 3);
70
74
  };
71
75
 
72
76
  // export for tests;
@@ -0,0 +1,80 @@
1
+
2
+ var Trie = require('../trie/trie');
3
+
4
+ // Probabilistic spellchecker based on http://norvig.com/spell-correct.html
5
+ // The general idea is simple. Given a word, the spellchecker calculates all strings that are some user-defined edit distance away. Of those many candidates, it filters the ones that are not words and then returns an array of possible corrections in order of decreasing probability, based on the edit distance and the candidate's frequency in the input corpus
6
+ // Words that are an edit distance of n away from the mispelled word are considered infinitely more probable than words that are of an edit distance >n
7
+
8
+ // wordlist is a corpus (an array) from which word probabilities are calculated (so something like /usr/share/dict/words (on OSX) will work okay, but real world text will work better)
9
+ function Spellcheck(wordlist) {
10
+ this.trie = new Trie();
11
+ this.trie.addStrings(wordlist);
12
+ this.word2frequency = {};
13
+ for(var i in wordlist) {
14
+ if(!this.word2frequency[wordlist[i]]) {
15
+ this.word2frequency[wordlist[i]] = 0;
16
+ }
17
+ this.word2frequency[wordlist[i]]++;
18
+ }
19
+ }
20
+
21
+ Spellcheck.prototype.isCorrect = function(word) {
22
+ return this.trie.contains(word);
23
+ }
24
+
25
+ // Returns a list of suggested corrections, from highest to lowest probability
26
+ // maxDistance is the maximum edit distance
27
+ // According to Norvig, literature suggests that 80% to 95% of spelling errors are an edit distance of 1 away from the correct word. This is good, because there are roughly 54n+25 strings 1 edit distance away from any given string of length n. So after maxDistance = 2, this becomes very slow.
28
+ Spellcheck.prototype.getCorrections = function(word, maxDistance) {
29
+ var self = this;
30
+ if(!maxDistance) maxDistance = 1;
31
+ var edits = this.editsWithMaxDistance(word, maxDistance);
32
+ edits = edits.slice(1,edits.length);
33
+ edits = edits.map(function(editList) {
34
+ return editList.filter(function(word) { return self.isCorrect(word); })
35
+ .map(function(word) { return [word, self.word2frequency[word]]; })
36
+ .sort(function(a,b) { return a[1] > b[1] ? -1 : 1; })
37
+ .map(function(wordscore) { return wordscore[0]; });
38
+ });
39
+ var flattened = [];
40
+ for(var i in edits) {
41
+ if(edits[i].length) flattened = flattened.concat(edits[i]);
42
+ }
43
+ return flattened.filter(function (v, i, a) { return a.indexOf(v) == i });
44
+ }
45
+
46
+ // Returns all edits that are 1 edit-distance away from the input word
47
+ Spellcheck.prototype.edits = function(word) {
48
+ var alphabet = 'abcdefghijklmnopqrstuvwxyz';
49
+ var edits = [];
50
+ for(var i=0; i<word.length+1; i++) {
51
+ if(i>0) edits.push(word.slice(0,i-1)+word.slice(i,word.length)); // deletes
52
+ if(i>0 && i<word.length+1) edits.push(word.slice(0,i-1) + word.slice(i,i+1) + word.slice(i-1, i) + word.slice(i+1,word.length)); // transposes
53
+ for(var k=0; k<alphabet.length; k++) {
54
+ if(i>0) edits.push(word.slice(0,i-1)+alphabet[k]+word.slice(i,word.length)); // replaces
55
+ edits.push(word.slice(0,i)+alphabet[k]+word.slice(i,word.length)); // inserts
56
+ }
57
+ }
58
+ // Deduplicate edits
59
+ edits = edits.filter(function (v, i, a) { return a.indexOf(v) == i });
60
+ return edits;
61
+ }
62
+
63
+ // Returns all edits that are up to "distance" edit distance away from the input word
64
+ Spellcheck.prototype.editsWithMaxDistance = function(word, distance) {
65
+ return this.editsWithMaxDistanceHelper(distance, [[word]]);
66
+ }
67
+
68
+ Spellcheck.prototype.editsWithMaxDistanceHelper = function(distanceCounter, distance2edits) {
69
+ if(distanceCounter == 0) return distance2edits;
70
+ var currentDepth = distance2edits.length-1;
71
+ var words = distance2edits[currentDepth];
72
+ var edits = this.edits(words[0]);
73
+ distance2edits[currentDepth+1] = [];
74
+ for(var i in words) {
75
+ distance2edits[currentDepth+1] = distance2edits[currentDepth+1].concat(this.edits(words[i]));
76
+ }
77
+ return this.editsWithMaxDistanceHelper(distanceCounter-1, distance2edits);
78
+ }
79
+
80
+ module.exports = Spellcheck;