natural 0.1.27 → 0.2.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.
Files changed (134) hide show
  1. package/.editorconfig +7 -0
  2. package/LICENSE.txt +0 -0
  3. package/Makefile +0 -0
  4. package/README.md +237 -24
  5. package/benchmarks/index.js +1 -1
  6. package/benchmarks/metaphone.js +0 -0
  7. package/benchmarks/soundex.js +0 -0
  8. package/examples/classification/basic.js +0 -0
  9. package/examples/classification/recall.js +0 -0
  10. package/examples/classification/store.js +0 -0
  11. package/examples/inflection/count.js +0 -0
  12. package/examples/inflection/noun.js +0 -0
  13. package/examples/inflection/noun_attach.js +0 -0
  14. package/examples/phonetics/compare.js +0 -0
  15. package/examples/phonetics/sounds_like.js +0 -0
  16. package/examples/phonetics/tokenize_and_phoneticize.js +0 -0
  17. package/examples/stemming/stem_corpus.js +0 -0
  18. package/examples/stemming/stem_word.js +0 -0
  19. package/examples/tfidf/array_example.js +0 -0
  20. package/examples/tfidf/multiple_terms.js +0 -0
  21. package/examples/tfidf/tfidf_example.js +0 -0
  22. package/index.js +0 -0
  23. package/io_spec/test_data/tfidf/one +0 -0
  24. package/io_spec/test_data/tfidf/two +0 -0
  25. package/io_spec/tfidf_spec.js +0 -0
  26. package/io_spec/wordnet_spec.js +0 -0
  27. package/lib/natural/analyzers/sentence_analyzer.js +16 -13
  28. package/lib/natural/classifiers/bayes_classifier.js +6 -2
  29. package/lib/natural/classifiers/classifier.js +0 -0
  30. package/lib/natural/classifiers/logistic_regression_classifier.js +0 -0
  31. package/lib/natural/distance/dice_coefficient.js +0 -0
  32. package/lib/natural/distance/jaro-winkler_distance.js +13 -8
  33. package/lib/natural/distance/levenshtein_distance.js +0 -0
  34. package/lib/natural/index.js +8 -0
  35. package/lib/natural/inflectors/count_inflector.js +0 -0
  36. package/lib/natural/inflectors/form_set.js +0 -0
  37. package/lib/natural/inflectors/fr/count_inflector.js +0 -0
  38. package/lib/natural/inflectors/fr/noun_inflector.js +0 -0
  39. package/lib/natural/inflectors/ja/noun_inflector.js +0 -0
  40. package/lib/natural/inflectors/noun_inflector.js +0 -0
  41. package/lib/natural/inflectors/present_verb_inflector.js +1 -0
  42. package/lib/natural/inflectors/singular_plural_inflector.js +1 -2
  43. package/lib/natural/ngrams/ngrams.js +45 -8
  44. package/lib/natural/ngrams/ngrams_zh.js +83 -0
  45. package/lib/natural/normalizers/normalizer.js +30 -38
  46. package/lib/natural/normalizers/normalizer_ja.js +26 -8
  47. package/lib/natural/normalizers/normalizer_no.js +59 -0
  48. package/lib/natural/phonetics/double_metaphone.js +9 -4
  49. package/lib/natural/phonetics/metaphone.js +6 -1
  50. package/lib/natural/phonetics/phonetic.js +0 -0
  51. package/lib/natural/phonetics/soundex.js +11 -7
  52. package/lib/natural/spellcheck/spellcheck.js +80 -0
  53. package/lib/natural/stemmers/lancaster_rules.js +4 -4
  54. package/lib/natural/stemmers/lancaster_stemmer.js +0 -0
  55. package/lib/natural/stemmers/porter_stemmer.js +90 -63
  56. package/lib/natural/stemmers/porter_stemmer_es.js +22 -23
  57. package/lib/natural/stemmers/porter_stemmer_fr.js +377 -0
  58. package/lib/natural/stemmers/porter_stemmer_no.js +155 -0
  59. package/lib/natural/stemmers/porter_stemmer_pt.js +302 -0
  60. package/lib/natural/stemmers/stemmer.js +0 -0
  61. package/lib/natural/stemmers/stemmer_fr.js +58 -0
  62. package/lib/natural/stemmers/stemmer_no.js +61 -0
  63. package/lib/natural/stemmers/stemmer_pt.js +65 -0
  64. package/lib/natural/stemmers/token.js +163 -0
  65. package/lib/natural/tfidf/tfidf.js +50 -19
  66. package/lib/natural/tokenizers/aggressive_tokenizer.js +0 -0
  67. package/lib/natural/tokenizers/aggressive_tokenizer_es.js +0 -0
  68. package/lib/natural/tokenizers/aggressive_tokenizer_fr.js +36 -0
  69. package/lib/natural/tokenizers/aggressive_tokenizer_nl.js +0 -0
  70. package/lib/natural/tokenizers/aggressive_tokenizer_no.js +39 -0
  71. package/lib/natural/tokenizers/aggressive_tokenizer_pt.js +0 -0
  72. package/lib/natural/tokenizers/regexp_tokenizer.js +0 -0
  73. package/lib/natural/tokenizers/tokenizer.js +2 -2
  74. package/lib/natural/tokenizers/treebank_word_tokenizer.js +0 -0
  75. package/lib/natural/trie/trie.js +47 -0
  76. package/lib/natural/util/bag.js +50 -0
  77. package/lib/natural/util/edge_weighted_digraph.js +112 -0
  78. package/lib/natural/util/longest_path_tree.js +99 -0
  79. package/lib/natural/util/shortest_path_tree.js +100 -0
  80. package/lib/natural/util/stopwords.js +0 -0
  81. package/lib/natural/util/stopwords_fr.js +194 -0
  82. package/lib/natural/util/stopwords_nl.js +43 -0
  83. package/lib/natural/util/stopwords_no.js +42 -0
  84. package/lib/natural/util/stopwords_pt.js +136 -0
  85. package/lib/natural/util/topological.js +98 -0
  86. package/lib/natural/wordnet/data_file.js +0 -0
  87. package/lib/natural/wordnet/index_file.js +0 -0
  88. package/lib/natural/wordnet/wordnet.js +0 -0
  89. package/lib/natural/wordnet/wordnet_file.js +0 -0
  90. package/package.json +2 -2
  91. package/spec/aggressive_tokenizer_fr_spec.js +101 -0
  92. package/spec/aggressive_tokenizer_pt_spec.js +0 -0
  93. package/spec/aggressive_tokenizer_spec.js +0 -0
  94. package/spec/bayes_classifier_spec.js +12 -0
  95. package/spec/classifier_spec.js +84 -0
  96. package/spec/count_inflector_fr_spec.js +0 -0
  97. package/spec/count_inflector_spec.js +0 -0
  98. package/spec/dice_coefficient_spec.js +0 -0
  99. package/spec/double_metaphone_spec.js +10 -1
  100. package/spec/jaro-winkler_spec.js +0 -0
  101. package/spec/lancaster_stemmer_spec.js +10 -1
  102. package/spec/levenshtein_spec.js +0 -0
  103. package/spec/logistic_regression_classifier_spec.js +0 -0
  104. package/spec/longest_path_tree_spec.js +102 -0
  105. package/spec/metaphone_spec.js +10 -0
  106. package/spec/ngram_spec.js +61 -0
  107. package/spec/ngram_zh_spec.js +122 -0
  108. package/spec/normalizer_ja_spec.js +9 -3
  109. package/spec/normalizer_no_spec.js +111 -0
  110. package/spec/normalizer_spec.js +26 -5
  111. package/spec/noun_inflector_fr_spec.js +0 -0
  112. package/spec/noun_inflector_ja_spec.js +0 -0
  113. package/spec/noun_inflector_spec.js +0 -0
  114. package/spec/porter_stemmer_fr_spec.js +101 -0
  115. package/spec/porter_stemmer_no_spec.js +143 -0
  116. package/spec/porter_stemmer_pt_spec.js +50 -0
  117. package/spec/porter_stemmer_spec.js +49 -0
  118. package/spec/present_verb_inflector_spec.js +3 -0
  119. package/spec/remove_diacritics_spec.js +31 -0
  120. package/spec/sentence_analyzer_spec.js +248 -0
  121. package/spec/shortest_path_tree_spec.js +132 -0
  122. package/spec/soundex_spec.js +13 -9
  123. package/spec/spellcheck_spec.js +92 -0
  124. package/spec/stemmer_token_spec.js +161 -0
  125. package/spec/test_data/snowball_fr.txt +20402 -0
  126. package/spec/test_data/snowball_no.txt +20628 -0
  127. package/spec/test_data/snowball_pt.txt +32016 -0
  128. package/spec/test_data/tfdif_testcorpus.txt +0 -0
  129. package/spec/test_data/tfidf_teststopwords.txt +0 -0
  130. package/spec/test_data/wordnet_mini.sql +0 -0
  131. package/spec/tfidf_spec.js +153 -28
  132. package/spec/tokenizer_ja_spec.js +0 -0
  133. package/spec/treebank_word_tokenizer_spec.js +0 -0
  134. package/spec/trie_spec.js +27 -3
@@ -24,19 +24,25 @@ var _ = require("underscore")._,
24
24
  Tokenizer = require('../tokenizers/regexp_tokenizer').WordTokenizer,
25
25
  tokenizer = new Tokenizer();
26
26
 
27
- exports.ngrams = function(sequence, n) {
28
- return ngrams(sequence, n);
27
+ exports.setTokenizer = function(t) {
28
+ if(!_.isFunction(t.tokenize))
29
+ throw new Error('Expected a valid Tokenizer');
30
+ tokenizer = t;
29
31
  }
30
32
 
31
- exports.bigrams = function(sequence) {
32
- return ngrams(sequence, 2);
33
+ exports.ngrams = function(sequence, n, startSymbol, endSymbol) {
34
+ return ngrams(sequence, n, startSymbol, endSymbol);
33
35
  }
34
36
 
35
- exports.trigrams = function(sequence) {
36
- return ngrams(sequence, 3);
37
+ exports.bigrams = function(sequence, startSymbol, endSymbol) {
38
+ return ngrams(sequence, 2, startSymbol, endSymbol);
37
39
  }
38
40
 
39
- var ngrams = function(sequence, n) {
41
+ exports.trigrams = function(sequence, startSymbol, endSymbol) {
42
+ return ngrams(sequence, 3, startSymbol, endSymbol);
43
+ }
44
+
45
+ var ngrams = function(sequence, n, startSymbol, endSymbol) {
40
46
  var result = [];
41
47
 
42
48
  if (!_(sequence).isArray()) {
@@ -44,10 +50,41 @@ var ngrams = function(sequence, n) {
44
50
  }
45
51
 
46
52
  var count = _.max([0, sequence.length - n + 1]);
47
-
53
+
54
+ // Check for left padding
55
+ if(typeof startSymbol !== "undefined" && startSymbol !== null) {
56
+ // Create an array of (n) start symbols
57
+ var blanks = [];
58
+ for(var i = 0 ; i < n ; i++) {
59
+ blanks.push(startSymbol);
60
+ }
61
+
62
+ // Create the left padding
63
+ for(var p = n - 1 ; p > 0 ; p--) {
64
+ // Create a tuple of (p) start symbols and (n - p) words
65
+ result.push(blanks.slice(0, p).concat(sequence.slice(0, n - p)));
66
+ }
67
+ }
68
+
69
+ // Build the complete ngrams
48
70
  for (var i = 0; i < count; i++) {
49
71
  result.push(sequence.slice(i, i + n));
50
72
  }
73
+
74
+ // Check for right padding
75
+ if(typeof endSymbol !== "undefined" && endSymbol !== null) {
76
+ // Create an array of (n) end symbols
77
+ var blanks = [];
78
+ for(var i = 0 ; i < n ; i++) {
79
+ blanks.push(endSymbol);
80
+ }
81
+
82
+ // create the right padding
83
+ for(var p = n - 1 ; p > 0 ; p--) {
84
+ // Create a tuple of (p) start symbols and (n - p) words
85
+ result.push(sequence.slice(sequence.length - p, sequence.length).concat(blanks.slice(0, n - p)));
86
+ }
87
+ }
51
88
 
52
89
  return result;
53
90
  }
@@ -0,0 +1,83 @@
1
+ /*
2
+ Copyright (c) 2014, Lee Wenzhu
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in
12
+ all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ THE SOFTWARE.
21
+ */
22
+
23
+ var _ = require("underscore")._;
24
+
25
+ exports.ngrams = function(sequence, n, startSymbol, endSymbol) {
26
+ return ngrams(sequence, n, startSymbol, endSymbol);
27
+ }
28
+
29
+ exports.bigrams = function(sequence, startSymbol, endSymbol) {
30
+ return ngrams(sequence, 2, startSymbol, endSymbol);
31
+ }
32
+
33
+ exports.trigrams = function(sequence, startSymbol, endSymbol) {
34
+ return ngrams(sequence, 3, startSymbol, endSymbol);
35
+ }
36
+
37
+ var ngrams = function(sequence, n, startSymbol, endSymbol) {
38
+ var result = [], i;
39
+
40
+ if (!_(sequence).isArray()) {
41
+ sequence = sequence.split('');
42
+ }
43
+
44
+ var count = _.max([0, sequence.length - n + 1]);
45
+
46
+ // Check for left padding
47
+ if(typeof startSymbol !== "undefined" && startSymbol !== null) {
48
+ // Create an array of (n) start symbols
49
+ var blanks = [];
50
+ for(i = 0 ; i < n ; i++) {
51
+ blanks.push(startSymbol);
52
+ }
53
+
54
+ // Create the left padding
55
+ for(var p = n - 1 ; p > 0 ; p--) {
56
+ // Create a tuple of (p) start symbols and (n - p) words
57
+ result.push(blanks.slice(0, p).concat(sequence.slice(0, n - p)));
58
+ }
59
+ }
60
+
61
+ // Build the complete ngrams
62
+ for (i = 0; i < count; i++) {
63
+ result.push(sequence.slice(i, i + n));
64
+ }
65
+
66
+ // Check for right padding
67
+ if(typeof endSymbol !== "undefined" && endSymbol !== null) {
68
+ // Create an array of (n) end symbols
69
+ var blanks = [];
70
+ for(var i = 0 ; i < n ; i++) {
71
+ blanks.push(endSymbol);
72
+ }
73
+
74
+ // create the right padding
75
+ for(var p = n - 1 ; p > 0 ; p--) {
76
+ // Create a tuple of (p) start symbols and (n - p) words
77
+ result.push(sequence.slice(sequence.length - p, sequence.length).concat(blanks.slice(0, n - p)));
78
+ }
79
+ }
80
+
81
+ return result;
82
+ };
83
+
@@ -30,7 +30,7 @@
30
30
  var replacer = require('../util/utils').replacer;
31
31
 
32
32
  var conversionTable = {
33
- "can't":"cannot",
33
+ "can't":"can not",
34
34
  "won't":"will not",
35
35
  "couldn't've":"could not have",
36
36
  "i'm":"I am",
@@ -43,7 +43,7 @@ var rules = [
43
43
  { regex: /([azAZ]*)\'[lL][lL]/g, output: "$1 will" },
44
44
  { regex: /([azAZ]*)\'[rR][eE]/g, output: "$1 are" },
45
45
  { regex: /([azAZ]*)\'[vV][eE]/g, output: "$1 have" },
46
- { regex: /([azAZ]*)\'[dD]/g, output: "$1 would" },
46
+ { regex: /([azAZ]*)\'[dD]/g, output: "$1 would" }
47
47
  ];
48
48
 
49
49
  // Accepts a list of tokens to expand.
@@ -51,44 +51,36 @@ var normalize_tokens = function(tokens) {
51
51
  if(typeof tokens === "string") {
52
52
  tokens = [tokens];
53
53
  }
54
+ var results = [];
54
55
  var rule_count = rules.length;
55
56
  var num_tokens = tokens.length;
56
- for(var i = 0 ; i < num_tokens ; i++) {
57
- var token = tokens[i];
58
-
59
- // check the covnersion table.
60
- if(conversionTable[token.toLowerCase()]) {
61
- tokens.splice.apply(tokens, [i, 1].concat(token.replace(token, conversionTable[token.toLowerCase()]).split(/\W+/)));
62
-
63
- // reset the tokens counter
64
- // NOTE: This may not be ideal, the idea is that messing with the array
65
- // could affect looping over it. Need to find out if theres ever going to
66
- // be a case where a rule conversion could result in another contraction which needs to
67
- // be expanded, if that is the case, we need this.
68
- i = 0;
69
- var num_tokens = tokens.length;
70
- }
71
- // Apply the rules
72
- else {
73
- for(var r = 0 ; r < rule_count ; r++) {
74
- var rule = rules[r];
75
- if(token.match(rule.regex)) {
76
- tokens.splice.apply(tokens, [i, 1].concat(token.replace(rule.regex, rule.output).split(/\W+/)));
77
-
78
- // reset the tokens counter
79
- // NOTE: This may not be ideal, the idea is that messing with the array
80
- // could affect looping over it. Need to find out if theres ever going to
81
- // be a case where a rule conversion could result in another contraction which needs to
82
- // be expanded, if that is the case, we need this.
83
- i = 0;
84
- var num_tokens = tokens.length;
85
- break ;
86
- }
87
- }
88
- }
89
- }
90
-
91
- return tokens;
57
+ var i, token, r, rule;
58
+
59
+ for (i = 0; i < num_tokens; i++) {
60
+ token = tokens[i];
61
+ // Check the conversion table
62
+ if (conversionTable[token.toLowerCase()]) {
63
+ results = results.concat(conversionTable[token.toLowerCase()].split(/\W+/));
64
+ }
65
+
66
+ // Apply the rules
67
+ else {
68
+ var matched = false;
69
+ for ( r = 0; r < rule_count; r++) {
70
+ rule = rules[r];
71
+ if (token.match(rule.regex)) {
72
+ results = results.concat(token.replace(rule.regex, rule.output).split(/\W+/));
73
+ matched = true;
74
+ break;
75
+ }
76
+ }
77
+ if (!matched) {
78
+ results.push(token);
79
+ }
80
+ }
81
+ }
82
+
83
+ return results;
92
84
  };
93
85
 
94
86
 
@@ -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
  },
@@ -0,0 +1,59 @@
1
+ /*
2
+ Copyright (c) 2014, Kristoffer Brabrand
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in
12
+ all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ THE SOFTWARE.
21
+ */
22
+
23
+ /**
24
+ * Remove commonly used diacritic marks from a string as these
25
+ * are not used in a consistent manner. Leave only ä, ö, ü.
26
+ */
27
+ var remove_diacritics = function(text) {
28
+ text = text.replace('à', 'a');
29
+ text = text.replace('À', 'A');
30
+ text = text.replace('á', 'a');
31
+ text = text.replace('Á', 'A');
32
+ text = text.replace('â', 'a');
33
+ text = text.replace('Â', 'A');
34
+ text = text.replace('ç', 'c');
35
+ text = text.replace('Ç', 'C');
36
+ text = text.replace('è', 'e');
37
+ text = text.replace('È', 'E');
38
+ text = text.replace('é', 'e');
39
+ text = text.replace('É', 'E');
40
+ text = text.replace('ê', 'e');
41
+ text = text.replace('Ê', 'E');
42
+ text = text.replace('î', 'i');
43
+ text = text.replace('Î', 'I');
44
+ text = text.replace('ñ', 'n');
45
+ text = text.replace('Ñ', 'N');
46
+ text = text.replace('ó', 'o');
47
+ text = text.replace('Ó', 'O');
48
+ text = text.replace('ô', 'o');
49
+ text = text.replace('Ô', 'O');
50
+ text = text.replace('û', 'u');
51
+ text = text.replace('Û', 'U');
52
+ text = text.replace('š', 's');
53
+ text = text.replace('Š', 'S');
54
+
55
+ return text;
56
+ };
57
+
58
+ // export the relevant stuff.
59
+ exports.remove_diacritics = remove_diacritics;
@@ -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');
@@ -61,6 +65,7 @@ function dropG(token) {
61
65
  }
62
66
 
63
67
  function transformG(token) {
68
+ token = token.replace(/gh/g, 'f');
64
69
  token = token.replace(/([^g]|^)(g)(i|e|y)/g, '$1j$3');
65
70
  token = token.replace(/gg/g, 'g');
66
71
  token = token.replace(/g/g, 'k');
@@ -69,7 +74,7 @@ function transformG(token) {
69
74
  }
70
75
 
71
76
  function dropH(token) {
72
- return token.replace(/([aeiou])h([^aeiou])/g, '$1$2');
77
+ return token.replace(/([aeiou])h([^aeiou]|$)/g, '$1$2');
73
78
  }
74
79
 
75
80
  function transformCK(token) {
File without changes
@@ -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;
@@ -80,7 +80,7 @@ exports.rules = {
80
80
  "size": "3"
81
81
  },
82
82
  {
83
- "appendage": "s",
83
+ "appendage": "ss",
84
84
  "continuation": false,
85
85
  "intact": false,
86
86
  "pattern": "ceed",
@@ -163,7 +163,7 @@ exports.rules = {
163
163
  "size": "2"
164
164
  },
165
165
  {
166
- "appendage": "c",
166
+ "appendage": "ct",
167
167
  "continuation": false,
168
168
  "intact": false,
169
169
  "pattern": "guish",
@@ -374,7 +374,7 @@ exports.rules = {
374
374
  "size": "4"
375
375
  },
376
376
  {
377
- "appendage": "c",
377
+ "appendage": "ct",
378
378
  "continuation": false,
379
379
  "intact": false,
380
380
  "pattern": "xion",
@@ -598,7 +598,7 @@ exports.rules = {
598
598
  "size": "2"
599
599
  },
600
600
  {
601
- "appendage": "i",
601
+ "appendage": "iv",
602
602
  "continuation": false,
603
603
  "intact": false,
604
604
  "pattern": "cept",
File without changes