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.
- package/.editorconfig +7 -0
- package/.npmignore +4 -11
- package/README.md +190 -50
- package/bayes_classifier.json +1 -0
- package/lib/natural/analyzers/sentence_analyzer.js +8 -5
- package/lib/natural/brill_pos_tagger/data/Dutch/README.txt +2 -0
- package/lib/natural/brill_pos_tagger/data/Dutch/brill_CONTEXTRULES.jg +285 -0
- package/lib/natural/brill_pos_tagger/data/Dutch/brill_LEXICON.jg +11699 -0
- package/lib/natural/brill_pos_tagger/data/English/README.txt +8 -0
- package/lib/natural/brill_pos_tagger/data/English/lexicon_from_posjs.json +297827 -0
- package/lib/natural/brill_pos_tagger/data/English/tr_from_brill_paper.txt +10 -0
- package/lib/natural/brill_pos_tagger/data/English/tr_from_posjs.txt +18 -0
- package/lib/natural/brill_pos_tagger/lib/Brill_POS_Tagger.js +136 -0
- package/lib/natural/brill_pos_tagger/lib/Predicate.js +410 -0
- package/lib/natural/brill_pos_tagger/lib/TF_Parser.js +717 -0
- package/lib/natural/brill_pos_tagger/lib/TF_Parser.pegjs +55 -0
- package/lib/natural/brill_pos_tagger/lib/TransformationRule.js +49 -0
- package/lib/natural/classifiers/bayes_classifier.js +6 -5
- package/lib/natural/classifiers/classifier.js +3 -2
- package/lib/natural/classifiers/logistic_regression_classifier.js +9 -5
- package/lib/natural/distance/dice_coefficient.js +22 -12
- package/lib/natural/distance/jaro-winkler_distance.js +13 -8
- package/lib/natural/index.js +6 -0
- package/lib/natural/inflectors/present_verb_inflector.js +1 -0
- package/lib/natural/ngrams/ngrams.js +6 -0
- package/lib/natural/normalizers/normalizer_ja.js +26 -8
- package/lib/natural/phonetics/double_metaphone.js +9 -4
- package/lib/natural/phonetics/metaphone.js +4 -0
- package/lib/natural/phonetics/soundex.js +11 -7
- package/lib/natural/spellcheck/spellcheck.js +80 -0
- package/lib/natural/stemmers/porter_stemmer.js +90 -62
- package/lib/natural/stemmers/porter_stemmer_fr.js +8 -3
- package/lib/natural/stemmers/porter_stemmer_pt.js +302 -0
- package/lib/natural/stemmers/stemmer.js +31 -6
- package/lib/natural/stemmers/stemmer_pt.js +65 -0
- package/lib/natural/stemmers/token.js +163 -0
- package/lib/natural/tfidf/tfidf.js +49 -18
- package/lib/natural/tokenizers/regexp_tokenizer.js +3 -3
- package/{examples/inflection/noun.js → lib/natural/tokenizers/sentence_tokenizer.js} +14 -10
- package/lib/natural/tokenizers/tokenizer_case.js +58 -0
- package/lib/natural/util/bag.js +1 -0
- package/lib/natural/util/stopwords_pt.js +136 -0
- package/lib/natural/wordnet/wordnet.js +2 -2
- package/lib/natural/wordnet/wordnet_file.js +1 -1
- package/lr_classifier.json +1 -0
- package/package.json +7 -4
- package/package.json~ +66 -0
- package/benchmarks/index.js +0 -32
- package/benchmarks/metaphone.js +0 -27
- package/benchmarks/soundex.js +0 -27
- package/examples/classification/basic.js +0 -35
- package/examples/classification/recall.js +0 -28
- package/examples/classification/store.js +0 -37
- package/examples/inflection/count.js +0 -35
- package/examples/inflection/noun_attach.js +0 -33
- package/examples/phonetics/compare.js +0 -39
- package/examples/phonetics/sounds_like.js +0 -40
- package/examples/phonetics/tokenize_and_phoneticize.js +0 -54
- package/examples/stemming/stem_corpus.js +0 -29
- package/examples/stemming/stem_word.js +0 -26
- package/examples/tfidf/array_example.js +0 -34
- package/examples/tfidf/multiple_terms.js +0 -33
- package/examples/tfidf/tfidf_example.js +0 -44
- package/io_spec/test_data/tfidf/one +0 -1
- package/io_spec/test_data/tfidf/two +0 -1
- package/io_spec/tfidf_spec.js +0 -50
- package/io_spec/wordnet_spec.js +0 -81
- package/spec/aggressive_tokenizer_es_spec.js +0 -36
- package/spec/aggressive_tokenizer_fr_spec.js +0 -101
- package/spec/aggressive_tokenizer_nl_spec.js +0 -36
- package/spec/aggressive_tokenizer_pt_spec.js +0 -56
- package/spec/aggressive_tokenizer_spec.js +0 -56
- package/spec/bayes_classifier_spec.js +0 -173
- package/spec/classifier_spec.js +0 -91
- package/spec/count_inflector_fr_spec.js +0 -44
- package/spec/count_inflector_spec.js +0 -73
- package/spec/dice_coefficient_spec.js +0 -26
- package/spec/double_metaphone_spec.js +0 -743
- package/spec/jaro-winkler_spec.js +0 -53
- package/spec/lancaster_stemmer_spec.js +0 -73
- package/spec/levenshtein_spec.js +0 -66
- package/spec/logistic_regression_classifier_spec.js +0 -122
- package/spec/longest_path_tree_spec.js +0 -102
- package/spec/metaphone_spec.js +0 -247
- package/spec/ngram_spec.js +0 -130
- package/spec/ngram_zh_spec.js +0 -122
- package/spec/normalizer_ja_spec.js +0 -154
- package/spec/normalizer_no_spec.js +0 -111
- package/spec/normalizer_spec.js +0 -118
- package/spec/noun_inflector_fr_spec.js +0 -476
- package/spec/noun_inflector_ja_spec.js +0 -112
- package/spec/noun_inflector_spec.js +0 -270
- package/spec/porter_stemmer_fr_spec.js +0 -101
- package/spec/porter_stemmer_it_spec.js +0 -44
- package/spec/porter_stemmer_no_spec.js +0 -143
- package/spec/porter_stemmer_ru_spec.js +0 -63
- package/spec/porter_stemmer_spec.js +0 -137
- package/spec/present_verb_inflector_spec.js +0 -125
- package/spec/sentence_analyzer_spec.js +0 -29
- package/spec/shortest_path_tree_spec.js +0 -132
- package/spec/soundex_spec.js +0 -154
- package/spec/stemmer_ja_spec.js +0 -66
- package/spec/test_data/snowball_fr.txt +0 -20402
- package/spec/test_data/snowball_it.txt +0 -35482
- package/spec/test_data/snowball_no.txt +0 -20628
- package/spec/test_data/tfdif_testcorpus.txt +0 -7
- package/spec/test_data/tfidf_document1.txt +0 -1
- package/spec/test_data/tfidf_document2.txt +0 -1
- package/spec/test_data/tfidf_document3.txt +0 -1
- package/spec/test_data/tfidf_document4.txt +0 -1
- package/spec/test_data/tfidf_teststopwords.txt +0 -1
- package/spec/test_data/wordnet_mini.sql +0 -95
- package/spec/tfidf_spec.js +0 -198
- package/spec/tokenizer_ja_spec.js +0 -58
- package/spec/transliterator_ja_spec.js +0 -103
- package/spec/treebank_word_tokenizer_spec.js +0 -36
- 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
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
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
|
|
52
|
-
var
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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;
|
package/lib/natural/index.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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)
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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;
|