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.
- package/.editorconfig +7 -0
- package/LICENSE.txt +0 -0
- package/Makefile +0 -0
- package/README.md +237 -24
- package/benchmarks/index.js +1 -1
- package/benchmarks/metaphone.js +0 -0
- package/benchmarks/soundex.js +0 -0
- package/examples/classification/basic.js +0 -0
- package/examples/classification/recall.js +0 -0
- package/examples/classification/store.js +0 -0
- package/examples/inflection/count.js +0 -0
- package/examples/inflection/noun.js +0 -0
- package/examples/inflection/noun_attach.js +0 -0
- package/examples/phonetics/compare.js +0 -0
- package/examples/phonetics/sounds_like.js +0 -0
- package/examples/phonetics/tokenize_and_phoneticize.js +0 -0
- package/examples/stemming/stem_corpus.js +0 -0
- package/examples/stemming/stem_word.js +0 -0
- package/examples/tfidf/array_example.js +0 -0
- package/examples/tfidf/multiple_terms.js +0 -0
- package/examples/tfidf/tfidf_example.js +0 -0
- package/index.js +0 -0
- package/io_spec/test_data/tfidf/one +0 -0
- package/io_spec/test_data/tfidf/two +0 -0
- package/io_spec/tfidf_spec.js +0 -0
- package/io_spec/wordnet_spec.js +0 -0
- package/lib/natural/analyzers/sentence_analyzer.js +16 -13
- package/lib/natural/classifiers/bayes_classifier.js +6 -2
- package/lib/natural/classifiers/classifier.js +0 -0
- package/lib/natural/classifiers/logistic_regression_classifier.js +0 -0
- package/lib/natural/distance/dice_coefficient.js +0 -0
- package/lib/natural/distance/jaro-winkler_distance.js +13 -8
- package/lib/natural/distance/levenshtein_distance.js +0 -0
- package/lib/natural/index.js +8 -0
- package/lib/natural/inflectors/count_inflector.js +0 -0
- package/lib/natural/inflectors/form_set.js +0 -0
- package/lib/natural/inflectors/fr/count_inflector.js +0 -0
- package/lib/natural/inflectors/fr/noun_inflector.js +0 -0
- package/lib/natural/inflectors/ja/noun_inflector.js +0 -0
- package/lib/natural/inflectors/noun_inflector.js +0 -0
- package/lib/natural/inflectors/present_verb_inflector.js +1 -0
- package/lib/natural/inflectors/singular_plural_inflector.js +1 -2
- package/lib/natural/ngrams/ngrams.js +45 -8
- package/lib/natural/ngrams/ngrams_zh.js +83 -0
- package/lib/natural/normalizers/normalizer.js +30 -38
- package/lib/natural/normalizers/normalizer_ja.js +26 -8
- package/lib/natural/normalizers/normalizer_no.js +59 -0
- package/lib/natural/phonetics/double_metaphone.js +9 -4
- package/lib/natural/phonetics/metaphone.js +6 -1
- package/lib/natural/phonetics/phonetic.js +0 -0
- package/lib/natural/phonetics/soundex.js +11 -7
- package/lib/natural/spellcheck/spellcheck.js +80 -0
- package/lib/natural/stemmers/lancaster_rules.js +4 -4
- package/lib/natural/stemmers/lancaster_stemmer.js +0 -0
- package/lib/natural/stemmers/porter_stemmer.js +90 -63
- package/lib/natural/stemmers/porter_stemmer_es.js +22 -23
- package/lib/natural/stemmers/porter_stemmer_fr.js +377 -0
- package/lib/natural/stemmers/porter_stemmer_no.js +155 -0
- package/lib/natural/stemmers/porter_stemmer_pt.js +302 -0
- package/lib/natural/stemmers/stemmer.js +0 -0
- package/lib/natural/stemmers/stemmer_fr.js +58 -0
- package/lib/natural/stemmers/stemmer_no.js +61 -0
- package/lib/natural/stemmers/stemmer_pt.js +65 -0
- package/lib/natural/stemmers/token.js +163 -0
- package/lib/natural/tfidf/tfidf.js +50 -19
- package/lib/natural/tokenizers/aggressive_tokenizer.js +0 -0
- package/lib/natural/tokenizers/aggressive_tokenizer_es.js +0 -0
- package/lib/natural/tokenizers/aggressive_tokenizer_fr.js +36 -0
- package/lib/natural/tokenizers/aggressive_tokenizer_nl.js +0 -0
- package/lib/natural/tokenizers/aggressive_tokenizer_no.js +39 -0
- package/lib/natural/tokenizers/aggressive_tokenizer_pt.js +0 -0
- package/lib/natural/tokenizers/regexp_tokenizer.js +0 -0
- package/lib/natural/tokenizers/tokenizer.js +2 -2
- package/lib/natural/tokenizers/treebank_word_tokenizer.js +0 -0
- package/lib/natural/trie/trie.js +47 -0
- package/lib/natural/util/bag.js +50 -0
- package/lib/natural/util/edge_weighted_digraph.js +112 -0
- package/lib/natural/util/longest_path_tree.js +99 -0
- package/lib/natural/util/shortest_path_tree.js +100 -0
- package/lib/natural/util/stopwords.js +0 -0
- package/lib/natural/util/stopwords_fr.js +194 -0
- package/lib/natural/util/stopwords_nl.js +43 -0
- package/lib/natural/util/stopwords_no.js +42 -0
- package/lib/natural/util/stopwords_pt.js +136 -0
- package/lib/natural/util/topological.js +98 -0
- package/lib/natural/wordnet/data_file.js +0 -0
- package/lib/natural/wordnet/index_file.js +0 -0
- package/lib/natural/wordnet/wordnet.js +0 -0
- package/lib/natural/wordnet/wordnet_file.js +0 -0
- package/package.json +2 -2
- package/spec/aggressive_tokenizer_fr_spec.js +101 -0
- package/spec/aggressive_tokenizer_pt_spec.js +0 -0
- package/spec/aggressive_tokenizer_spec.js +0 -0
- package/spec/bayes_classifier_spec.js +12 -0
- package/spec/classifier_spec.js +84 -0
- package/spec/count_inflector_fr_spec.js +0 -0
- package/spec/count_inflector_spec.js +0 -0
- package/spec/dice_coefficient_spec.js +0 -0
- package/spec/double_metaphone_spec.js +10 -1
- package/spec/jaro-winkler_spec.js +0 -0
- package/spec/lancaster_stemmer_spec.js +10 -1
- package/spec/levenshtein_spec.js +0 -0
- package/spec/logistic_regression_classifier_spec.js +0 -0
- package/spec/longest_path_tree_spec.js +102 -0
- package/spec/metaphone_spec.js +10 -0
- package/spec/ngram_spec.js +61 -0
- package/spec/ngram_zh_spec.js +122 -0
- package/spec/normalizer_ja_spec.js +9 -3
- package/spec/normalizer_no_spec.js +111 -0
- package/spec/normalizer_spec.js +26 -5
- package/spec/noun_inflector_fr_spec.js +0 -0
- package/spec/noun_inflector_ja_spec.js +0 -0
- package/spec/noun_inflector_spec.js +0 -0
- package/spec/porter_stemmer_fr_spec.js +101 -0
- package/spec/porter_stemmer_no_spec.js +143 -0
- package/spec/porter_stemmer_pt_spec.js +50 -0
- package/spec/porter_stemmer_spec.js +49 -0
- package/spec/present_verb_inflector_spec.js +3 -0
- package/spec/remove_diacritics_spec.js +31 -0
- package/spec/sentence_analyzer_spec.js +248 -0
- package/spec/shortest_path_tree_spec.js +132 -0
- package/spec/soundex_spec.js +13 -9
- package/spec/spellcheck_spec.js +92 -0
- package/spec/stemmer_token_spec.js +161 -0
- package/spec/test_data/snowball_fr.txt +20402 -0
- package/spec/test_data/snowball_no.txt +20628 -0
- package/spec/test_data/snowball_pt.txt +32016 -0
- package/spec/test_data/tfdif_testcorpus.txt +0 -0
- package/spec/test_data/tfidf_teststopwords.txt +0 -0
- package/spec/test_data/wordnet_mini.sql +0 -0
- package/spec/tfidf_spec.js +153 -28
- package/spec/tokenizer_ja_spec.js +0 -0
- package/spec/treebank_word_tokenizer_spec.js +0 -0
- 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.
|
|
28
|
-
|
|
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.
|
|
32
|
-
return ngrams(sequence,
|
|
33
|
+
exports.ngrams = function(sequence, n, startSymbol, endSymbol) {
|
|
34
|
+
return ngrams(sequence, n, startSymbol, endSymbol);
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
exports.
|
|
36
|
-
return ngrams(sequence,
|
|
37
|
+
exports.bigrams = function(sequence, startSymbol, endSymbol) {
|
|
38
|
+
return ngrams(sequence, 2, startSymbol, endSymbol);
|
|
37
39
|
}
|
|
38
40
|
|
|
39
|
-
|
|
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":"
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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
|
},
|
|
@@ -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
|
-
|
|
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');
|
|
@@ -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)
|
|
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;
|
|
@@ -80,7 +80,7 @@ exports.rules = {
|
|
|
80
80
|
"size": "3"
|
|
81
81
|
},
|
|
82
82
|
{
|
|
83
|
-
"appendage": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
601
|
+
"appendage": "iv",
|
|
602
602
|
"continuation": false,
|
|
603
603
|
"intact": false,
|
|
604
604
|
"pattern": "cept",
|
|
File without changes
|