natural 0.5.4 → 0.6.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/.dockerignore +2 -0
- package/.npmignore +1 -0
- package/.travis.yml +1 -1
- package/README.md +412 -89
- package/bayes_classifier.json +1 -1
- package/classifier.json +418 -0
- package/data/kata-dasar.txt +29933 -0
- package/lib/natural/brill_pos_tagger/lib/Brill_POS_Tagger.js +8 -9
- package/lib/natural/brill_pos_tagger/lib/Brill_POS_Tester.js +5 -5
- package/lib/natural/brill_pos_tagger/lib/Brill_POS_Trainer.js +10 -6
- package/lib/natural/brill_pos_tagger/lib/Corpus.js +21 -16
- package/lib/natural/brill_pos_tagger/lib/Lexicon.js +22 -13
- package/lib/natural/brill_pos_tagger/lib/Predicate.js +2 -2
- package/lib/natural/brill_pos_tagger/lib/RuleTemplate.js +2 -2
- package/lib/natural/brill_pos_tagger/lib/RuleTemplates.js +132 -146
- package/lib/natural/brill_pos_tagger/lib/Sentence.js +41 -0
- package/lib/natural/brill_pos_tagger/lib/TF_Parser.js +83 -76
- package/lib/natural/brill_pos_tagger/lib/TF_Parser.pegjs +19 -4
- package/lib/natural/brill_pos_tagger/lib/TransformationRule.js +10 -11
- package/lib/natural/classifiers/classifier.js +15 -8
- package/lib/natural/classifiers/maxent/Classifier.js +125 -0
- package/lib/natural/classifiers/maxent/Context.js +33 -0
- package/lib/natural/classifiers/maxent/Distribution.js +145 -0
- package/lib/natural/classifiers/maxent/Element.js +34 -0
- package/lib/natural/classifiers/maxent/Feature.js +88 -0
- package/lib/natural/classifiers/maxent/FeatureSet.js +67 -0
- package/lib/natural/classifiers/maxent/GISScaler.js +148 -0
- package/lib/natural/classifiers/maxent/POS/ME_Corpus.js +56 -0
- package/lib/natural/classifiers/maxent/POS/ME_Sentence.js +73 -0
- package/lib/natural/classifiers/maxent/POS/POS_Element.js +172 -0
- package/lib/natural/classifiers/maxent/Sample.js +141 -0
- package/lib/natural/classifiers/maxent/SimpleExample/SE_Element.js +49 -0
- package/lib/natural/distance/hamming_distance.js +52 -0
- package/lib/natural/distance/jaro-winkler_distance.js +16 -6
- package/lib/natural/distance/levenshtein_distance.js +164 -11
- package/lib/natural/index.js +26 -5
- package/lib/natural/inflectors/noun_inflector.js +15 -13
- package/lib/natural/ngrams/ngrams.js +83 -25
- package/lib/natural/normalizers/normalizer_sv.js +41 -0
- package/lib/natural/sentiment/Basque/senticon_eu.json +21557 -0
- package/lib/natural/sentiment/Catalan/senticon_ca.json +36352 -0
- package/lib/natural/sentiment/Dutch/negations_du.json +3 -0
- package/lib/natural/sentiment/Dutch/pattern-sentiment-nl.json +39650 -0
- package/lib/natural/sentiment/English/negations_en.json +3 -0
- package/lib/natural/sentiment/English/pattern-sentiment-en.json +16024 -0
- package/lib/natural/sentiment/English/senticon_en.json +124197 -0
- package/lib/natural/sentiment/French/pattern-sentiment-fr.json +40906 -0
- package/lib/natural/sentiment/Galician/senticon_gl.json +24427 -0
- package/lib/natural/sentiment/Italian/pattern-sentiment-it.json +24522 -0
- package/lib/natural/sentiment/SentimentAnalyzer.js +116 -0
- package/lib/natural/sentiment/Spanish/afinnShortSortedSpanish.json +1655 -0
- package/lib/natural/sentiment/Spanish/negations_es.json +3 -0
- package/lib/natural/sentiment/Spanish/senticon_es.json +56722 -0
- package/lib/natural/sentiment/tools/README.md +31 -0
- package/lib/natural/sentiment/tools/XmlParser4PatternData.js +31 -0
- package/lib/natural/sentiment/tools/sentimentXmlParser.js +30 -0
- package/lib/natural/stemmers/indonesian/base_stemmer_id.js +86 -0
- package/lib/natural/stemmers/indonesian/prefix_rules.js +1027 -0
- package/lib/natural/stemmers/indonesian/removal.js +49 -0
- package/lib/natural/stemmers/indonesian/stemmer_id.js +307 -0
- package/lib/natural/stemmers/indonesian/suffix_rules.js +75 -0
- package/lib/natural/stemmers/porter_stemmer_es.js +265 -199
- package/lib/natural/stemmers/porter_stemmer_fr.js +1 -1
- package/lib/natural/stemmers/porter_stemmer_nl.js +362 -0
- package/lib/natural/stemmers/porter_stemmer_sv.js +99 -0
- package/lib/natural/stemmers/stemmer_es.js +18 -12
- package/lib/natural/stemmers/stemmer_nl.js +64 -0
- package/lib/natural/stemmers/stemmer_sv.js +61 -0
- package/lib/natural/tfidf/tfidf.js +27 -4
- package/lib/natural/tokenizers/aggressive_tokenizer_id.js +44 -0
- package/lib/natural/tokenizers/aggressive_tokenizer_sv.js +40 -0
- package/lib/natural/tokenizers/aggressive_tokenizer_vi.js +37 -0
- package/lib/natural/tokenizers/orthography_matchers.js +33 -0
- package/lib/natural/tokenizers/regexp_tokenizer.js +23 -3
- package/lib/natural/tokenizers/sentence_tokenizer.js +1 -1
- package/lib/natural/util/stopwords_id.js +119 -0
- package/lib/natural/util/stopwords_no.js +1 -1
- package/lib/natural/util/stopwords_sv.js +70 -0
- package/lib/natural/wordnet/wordnet_file.js +7 -1
- package/lr_classifier.json +1 -1
- package/package-lock.json +378 -0
- package/package.json +29 -21
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Element class for POS tagging
|
|
3
|
+
Copyright (C) 2018 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
|
+
|
|
20
|
+
var util = require('util');
|
|
21
|
+
var Element = require('../Element');
|
|
22
|
+
var Feature = require('../Feature');
|
|
23
|
+
|
|
24
|
+
function POS_Element(a, b) {
|
|
25
|
+
POS_Element.super_.call(this, a, b);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
util.inherits(POS_Element, Element);
|
|
29
|
+
|
|
30
|
+
POS_Element.prototype.generateFeatures = function(featureSet) {
|
|
31
|
+
var context = this.b.data;
|
|
32
|
+
var tag = this.a;
|
|
33
|
+
var token = context.wordWindow["0"];
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
// Feature for the current word
|
|
37
|
+
function currentWord(x) {
|
|
38
|
+
if ((x.b.data.wordWindow["0"] === token) &&
|
|
39
|
+
(x.a === tag)) {
|
|
40
|
+
return 1;
|
|
41
|
+
}
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
|
44
|
+
featureSet.addFeature(new Feature(currentWord, "wordFeature", ["0", token, "0", tag]));
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
// Feature for previous bigram (previous two tags), positions -2, -1
|
|
48
|
+
if (context.tagWindow["-2"]) {
|
|
49
|
+
var prevPrevTag = context.tagWindow["-2"];
|
|
50
|
+
var prevTag = context.tagWindow["-1"];
|
|
51
|
+
function prevBigram(x) {
|
|
52
|
+
if ((x.a === tag) &&
|
|
53
|
+
(x.b.data.tagWindow["-2"] === prevPrevTag) &&
|
|
54
|
+
(x.b.data.tagWindow["-1"] === prevTag)) {
|
|
55
|
+
return 1;
|
|
56
|
+
}
|
|
57
|
+
return 0;
|
|
58
|
+
}
|
|
59
|
+
featureSet.addFeature(new Feature(prevBigram, "prevBigram", ["0", tag, "-2", prevPrevTag, "-1", prevTag]));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
/*
|
|
64
|
+
// Feature for left bigram, positions -1, 0
|
|
65
|
+
if (context.tagWindow["-1"]) {
|
|
66
|
+
var prevTag = context.tagWindow["-1"];
|
|
67
|
+
function leftBigram(x) {
|
|
68
|
+
if ((x.b.data.tagWindow["-1"] === prevTag) &&
|
|
69
|
+
(x.a === tag)) {
|
|
70
|
+
return 1;
|
|
71
|
+
}
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
74
|
+
featureSet.addFeature(new Feature(leftBigram, "leftBigram", ["0", tag, "-1", prevTag]));
|
|
75
|
+
}
|
|
76
|
+
*/
|
|
77
|
+
|
|
78
|
+
/*
|
|
79
|
+
|
|
80
|
+
// Feature for right bigram, positions 0, 1
|
|
81
|
+
if (context.tagWindow["1"]) {
|
|
82
|
+
var nextTag = context.tagWindow["1"];
|
|
83
|
+
function rightBigram(x) {
|
|
84
|
+
if ((x.a === tag) &&
|
|
85
|
+
(x.b.data.tagWindow["1"] === nextTag)) {
|
|
86
|
+
return 1;
|
|
87
|
+
}
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
|
90
|
+
featureSet.addFeature(new Feature(rightBigram, "rightBigram", ["0", tag, "1", nextTag]));
|
|
91
|
+
}
|
|
92
|
+
*/
|
|
93
|
+
/*
|
|
94
|
+
// Feature for next bigram (next two tags), positions 1 and 2
|
|
95
|
+
if (context.tagWindow["2"]) {
|
|
96
|
+
var nextTag = context.tagWindow["1"];
|
|
97
|
+
var nextNextTag = context.tagWindow["2"];
|
|
98
|
+
function nextBigram(x) {
|
|
99
|
+
if ((x.a === tag) &&
|
|
100
|
+
(x.b.data.tagWindow["1"] === nextTag) &&
|
|
101
|
+
(x.b.data.tagWindow["2"] === nextNextTag)) {
|
|
102
|
+
return 1;
|
|
103
|
+
}
|
|
104
|
+
return 0;
|
|
105
|
+
}
|
|
106
|
+
featureSet.addFeature(new Feature(nextBigram, "nextBigram", ["0", tag, "1", nextTag, "2", nextNextTag]));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Feature that looks at the left bigram words
|
|
110
|
+
if (context.wordWindow["-1"]) {
|
|
111
|
+
var prevWord = context.wordWindow["-1"];
|
|
112
|
+
function leftBigramWords(x) {
|
|
113
|
+
if ((x.a === tag) &&
|
|
114
|
+
(x.b.data.wordWindow["0"] === token) &&
|
|
115
|
+
(x.b.data.wordWindow["-1"] === prevWord)) {
|
|
116
|
+
return 1;
|
|
117
|
+
}
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
featureSet.addFeature(new Feature(leftBigramWords, "leftBigramWords", ["0", tag, "0", token, "-1", prevWord]));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Feature that looks at the right bigram words
|
|
124
|
+
if (context.wordWindow["1"]) {
|
|
125
|
+
var nextWord = context.wordWindow["1"];
|
|
126
|
+
function rightBigramWords(x) {
|
|
127
|
+
if ((x.a === tag) &&
|
|
128
|
+
(x.b.data.wordWindow["0"] === token) &&
|
|
129
|
+
(x.b.data.wordWindow["1"] === nextWord)) {
|
|
130
|
+
return 1;
|
|
131
|
+
}
|
|
132
|
+
return 0;
|
|
133
|
+
}
|
|
134
|
+
featureSet.addFeature(new Feature(rightBigramWords, "rightBigramWords", ["0", tag, "0", token, "1", nextWord]));
|
|
135
|
+
}
|
|
136
|
+
*/
|
|
137
|
+
|
|
138
|
+
// Feature that looks at the previous word and its category
|
|
139
|
+
if (context.wordWindow["-1"]) {
|
|
140
|
+
var prevWord = context.wordWindow["-1"];
|
|
141
|
+
var prevTag = context.tagWindow["-1"];
|
|
142
|
+
function prevWordAndCat(x) {
|
|
143
|
+
if ((x.a === tag) &&
|
|
144
|
+
(x.b.data.wordWindow["-1"] === prevWord) &&
|
|
145
|
+
(x.b.data.tagWindow["-1"] === prevTag)) {
|
|
146
|
+
return 1;
|
|
147
|
+
}
|
|
148
|
+
return 0;
|
|
149
|
+
}
|
|
150
|
+
featureSet.addFeature(new Feature(prevWordAndCat, "prevWordAndCat", ["0", tag, "-1", prevWord, "-1", prevTag]));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
/*
|
|
155
|
+
// Feature that looks at the next word and its category
|
|
156
|
+
if (context.wordWindow["1"]) {
|
|
157
|
+
var nextWord = context.wordWindow["1"];
|
|
158
|
+
var nextTag = context.tagWindow["1"];
|
|
159
|
+
function nextWordAndCat(x) {
|
|
160
|
+
if ((x.a === tag) &&
|
|
161
|
+
(x.b.data.wordWindow["1"] === nextWord) &&
|
|
162
|
+
(x.b.data.tagWindow["1"] === nextTag)) {
|
|
163
|
+
return 1;
|
|
164
|
+
}
|
|
165
|
+
return 0;
|
|
166
|
+
}
|
|
167
|
+
featureSet.addFeature(new Feature(nextWordAndCat, "nextWordAndCat", ["0", tag, "1", nextWord, "1", nextTag]));
|
|
168
|
+
}
|
|
169
|
+
*/
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
module.exports = POS_Element;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Sample space of observed events
|
|
3
|
+
Copyright (C) 2018 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 Context = require('./Context');
|
|
20
|
+
|
|
21
|
+
var fs = require('fs');
|
|
22
|
+
|
|
23
|
+
function Sample(elements) {
|
|
24
|
+
this.frequencyOfContext = {};
|
|
25
|
+
this.frequency = {};
|
|
26
|
+
this.classes = [];
|
|
27
|
+
if (elements) {
|
|
28
|
+
this.elements = elements;
|
|
29
|
+
this.analyse();
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
this.elements = [];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Extracts classes and frequencies
|
|
37
|
+
Sample.prototype.analyse = function() {
|
|
38
|
+
var that = this;
|
|
39
|
+
this.elements.forEach(function(x) {
|
|
40
|
+
if (this.classes.indexOf(x.a) === -1) {
|
|
41
|
+
this.classes.push(x.a);
|
|
42
|
+
}
|
|
43
|
+
if (!that.frequencyOfContext[x.b.toString()]) {
|
|
44
|
+
that.frequencyOfContext[x.b.toString()] = 0;
|
|
45
|
+
}
|
|
46
|
+
that.frequencyOfContext[x.b.toString()]++;
|
|
47
|
+
if (!that.frequency[x.toString()]) {
|
|
48
|
+
that.frequency[x.toString()] = 0;
|
|
49
|
+
}
|
|
50
|
+
that.frequency[x.toString()]++;
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
Sample.prototype.addElement = function(x) {
|
|
55
|
+
this.elements.push(x);
|
|
56
|
+
// Update frequencies
|
|
57
|
+
if (!this.frequencyOfContext[x.b.toString()]) {
|
|
58
|
+
this.frequencyOfContext[x.b.toString()] = 0;
|
|
59
|
+
}
|
|
60
|
+
this.frequencyOfContext[x.b.toString()]++;
|
|
61
|
+
if (!this.frequency[x.toString()]) {
|
|
62
|
+
this.frequency[x.toString()] = 0;
|
|
63
|
+
}
|
|
64
|
+
this.frequency[x.toString()]++;
|
|
65
|
+
// Update classes
|
|
66
|
+
if (this.classes.indexOf(x.a) === -1) {
|
|
67
|
+
this.classes.push(x.a);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
Sample.prototype.observedProbabilityOfContext = function(context) {
|
|
72
|
+
if (this.frequencyOfContext[context.toString()]) {
|
|
73
|
+
return this.frequencyOfContext[context.toString()] / this.elements.length;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
return 0;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
Sample.prototype.observedProbability = function(x) {
|
|
81
|
+
if (this.frequency[x.toString()]) {
|
|
82
|
+
return this.frequency[x.toString()] / this.elements.length;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
return 0;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
Sample.prototype.size = function() {
|
|
90
|
+
return this.elements.length;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
Sample.prototype.getClasses = function() {
|
|
94
|
+
return this.classes;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
Sample.prototype.generateFeatures = function(featureSet) {
|
|
98
|
+
this.elements.forEach(function(x) {
|
|
99
|
+
x.generateFeatures(featureSet);
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
Sample.prototype.save = function(filename, callback) {
|
|
104
|
+
var sample = this;
|
|
105
|
+
var data = JSON.stringify(this, null, 2);
|
|
106
|
+
fs.writeFile(filename, data, 'utf8', function(err) {
|
|
107
|
+
//console.log('Sample written')
|
|
108
|
+
if(callback) {
|
|
109
|
+
callback(err, err ? null : sample);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// Loads a sample from file and revives the right classes, i.e. Sample and
|
|
115
|
+
// Element classes.
|
|
116
|
+
Sample.prototype.load = function(filename, elementClass, callback) {
|
|
117
|
+
fs.readFile(filename, 'utf8', function(err, data) {
|
|
118
|
+
|
|
119
|
+
if(!err) {
|
|
120
|
+
var sampleData = JSON.parse(data);
|
|
121
|
+
var sample = new Sample();
|
|
122
|
+
sampleData.elements.forEach(function(elementData) {
|
|
123
|
+
var elt = new elementClass(elementData.a, new Context(elementData.b.data));
|
|
124
|
+
sample.addElement(elt);
|
|
125
|
+
});
|
|
126
|
+
if (!sample.frequency || !sample.frequencyOfContext) {
|
|
127
|
+
sample.analyse();
|
|
128
|
+
}
|
|
129
|
+
if (callback) {
|
|
130
|
+
callback(err, sample);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
if (callback) {
|
|
135
|
+
callback(err);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
module.exports = Sample;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Simple Example Element class
|
|
3
|
+
Copyright (C) 2018 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 util = require('util');
|
|
20
|
+
|
|
21
|
+
var Element = require('../Element');
|
|
22
|
+
var Feature = require('../Feature');
|
|
23
|
+
|
|
24
|
+
function SE_Element(a, b) {
|
|
25
|
+
SE_Element.super_.call(this, a, b);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
util.inherits(SE_Element, Element);
|
|
29
|
+
|
|
30
|
+
SE_Element.prototype.generateFeatures = function(featureSet) {
|
|
31
|
+
|
|
32
|
+
function isZero(x) {
|
|
33
|
+
if ((x.a === "x") && (x.b.data === "0")) {
|
|
34
|
+
return 1;
|
|
35
|
+
}
|
|
36
|
+
return 0;
|
|
37
|
+
}
|
|
38
|
+
featureSet.addFeature(new Feature(isZero, "isZero", ["0"]));
|
|
39
|
+
|
|
40
|
+
function isOne(x) {
|
|
41
|
+
if ((x.a === "y") && (x.b.data === "1")) {
|
|
42
|
+
return 1;
|
|
43
|
+
}
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
featureSet.addFeature(new Feature(isOne, "isOne", ["1"]));
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
module.exports = SE_Element;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2018, Shane Caldwell, Hugo ter Doest
|
|
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
|
+
// Computes the Hamming distance between two string -- intrepreted from:
|
|
24
|
+
// https://en.wikipedia.org/wiki/Hamming_distance
|
|
25
|
+
// s1 is the first string to compare
|
|
26
|
+
// s2 is the second string to compare
|
|
27
|
+
// Strings should have equal length
|
|
28
|
+
function HammingDistance(s1, s2, ignoreCase) {
|
|
29
|
+
// Return -1 if one of the parameters is not a string
|
|
30
|
+
if (typeof(s1) != "string" || typeof(s2) != "string") {
|
|
31
|
+
return -1;
|
|
32
|
+
}
|
|
33
|
+
// Return -1 the lengths of the strings differ
|
|
34
|
+
if (s1.length != s2.length) {
|
|
35
|
+
return -1;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (ignoreCase) {
|
|
39
|
+
s1 = s1.toLowerCase();
|
|
40
|
+
s2 = s2.toLowerCase();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
var diffs = 0;
|
|
44
|
+
for (var i = 0; i < s1.length; i++) {
|
|
45
|
+
if (s1[i] != s2[i]) {
|
|
46
|
+
diffs++;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return diffs;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = HammingDistance;
|
|
@@ -35,8 +35,6 @@ function distance(s1, s2) {
|
|
|
35
35
|
return 0;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
s1 = s1.toLowerCase(), s2 = s2.toLowerCase();
|
|
39
|
-
|
|
40
38
|
var matchWindow = (Math.floor(Math.max(s1.length, s2.length) / 2.0)) - 1;
|
|
41
39
|
var matches1 = new Array(s1.length);
|
|
42
40
|
var matches2 = new Array(s2.length);
|
|
@@ -83,6 +81,7 @@ function distance(s1, s2) {
|
|
|
83
81
|
k++;
|
|
84
82
|
}
|
|
85
83
|
if (s1[i] !== s2[k]) { // if the characters don't match, increase transposition
|
|
84
|
+
// HtD: t is always less than the number of matches m, because transpositions are a subset of matches
|
|
86
85
|
t++;
|
|
87
86
|
}
|
|
88
87
|
k++; // iterate k index normally
|
|
@@ -91,7 +90,8 @@ function distance(s1, s2) {
|
|
|
91
90
|
// transpositions divided by 2
|
|
92
91
|
t = t / 2.0;
|
|
93
92
|
|
|
94
|
-
return ((m / s1.length) + (m / s2.length) + ((m - t) / m)) / 3.0;
|
|
93
|
+
return ((m / s1.length) + (m / s2.length) + ((m - t) / m)) / 3.0; // HtD: therefore, m - t > 0, and m - t < m
|
|
94
|
+
// HtD: => return value is between 0 and 1
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
// Computes the Winkler distance between two string -- intrepreted from:
|
|
@@ -99,19 +99,29 @@ function distance(s1, s2) {
|
|
|
99
99
|
// s1 is the first string to compare
|
|
100
100
|
// s2 is the second string to compare
|
|
101
101
|
// dj is the Jaro Distance (if you've already computed it), leave blank and the method handles it
|
|
102
|
-
|
|
102
|
+
// ignoreCase: if true strings are first converted to lower case before comparison
|
|
103
|
+
function JaroWinklerDistance(s1, s2, dj, ignoreCase) {
|
|
103
104
|
if (s1 === s2) {
|
|
104
105
|
return 1;
|
|
105
106
|
} else {
|
|
106
|
-
|
|
107
|
+
if (ignoreCase) {
|
|
108
|
+
s1 = s1.toLowerCase();
|
|
109
|
+
s2 = s2.toLowerCase();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
//console.log(news1);
|
|
113
|
+
//console.log(news2);
|
|
114
|
+
|
|
115
|
+
var jaro = (typeof(dj) === 'undefined') ? distance(s1, s2) : dj;
|
|
107
116
|
var p = 0.1; // default scaling factor
|
|
108
117
|
var l = 0 // length of the matching prefix
|
|
109
118
|
while(s1[l] === s2[l] && l < 4) {
|
|
110
119
|
l++;
|
|
111
120
|
}
|
|
112
121
|
|
|
122
|
+
// HtD: 1 - jaro >= 0
|
|
113
123
|
return jaro + l * p * (1 - jaro);
|
|
114
124
|
}
|
|
115
125
|
}
|
|
116
|
-
module.exports = JaroWinklerDistance;
|
|
117
126
|
|
|
127
|
+
module.exports = JaroWinklerDistance;
|
|
@@ -29,40 +29,193 @@ THE SOFTWARE.
|
|
|
29
29
|
* Algorithm based from Speech and Language Processing - Daniel Jurafsky and James H. Martin.
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
var _ = require('underscore');
|
|
33
|
+
|
|
34
|
+
// Walk the path back from the matchEnd to the beginning of the match.
|
|
35
|
+
// Do this by traversing the distanceMatrix as you would a linked list,
|
|
36
|
+
// following going from cell child to parent until reach row 0.
|
|
37
|
+
function _getMatchStart(distanceMatrix, matchEnd, sourceLength) {
|
|
38
|
+
var row = sourceLength;
|
|
39
|
+
var column = matchEnd;
|
|
40
|
+
var tmpRow;
|
|
41
|
+
var tmpColumn;
|
|
42
|
+
|
|
43
|
+
// match will be empty string
|
|
44
|
+
if (matchEnd === 0) { return 0; }
|
|
45
|
+
|
|
46
|
+
while(row > 1 && column > 1) {
|
|
47
|
+
tmpRow = row;
|
|
48
|
+
tmpColumn = column;
|
|
49
|
+
row = distanceMatrix[tmpRow][tmpColumn].parentCell.row;
|
|
50
|
+
column = distanceMatrix[tmpRow][tmpColumn].parentCell.column;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return column-1;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function getMinCostSubstring(distanceMatrix, source, target) {
|
|
57
|
+
var sourceLength = source.length;
|
|
58
|
+
var targetLength = target.length;
|
|
59
|
+
var minDistance = sourceLength + targetLength;
|
|
60
|
+
var matchEnd = targetLength;
|
|
61
|
+
|
|
62
|
+
// Find minimum value in last row of the cost matrix. This cell marks the
|
|
63
|
+
// end of the match string.
|
|
64
|
+
for (var column = 0; column <= targetLength; column++) {
|
|
65
|
+
if (minDistance > distanceMatrix[sourceLength][column].cost) {
|
|
66
|
+
minDistance = distanceMatrix[sourceLength][column].cost;
|
|
67
|
+
matchEnd = column;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
matchStart = _getMatchStart(distanceMatrix, matchEnd, sourceLength);
|
|
72
|
+
return {substring: target.slice(matchStart, matchEnd), distance: minDistance};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/*
|
|
76
|
+
* Returns the Damerau-Levenshtein distance between strings. Counts the distance
|
|
77
|
+
* between two strings by returning the number of edit operations required to
|
|
78
|
+
* convert `source` into `target`.
|
|
79
|
+
*
|
|
80
|
+
* Valid edit operations are:
|
|
81
|
+
* - transposition, insertion, deletion, and substitution
|
|
82
|
+
*
|
|
83
|
+
* Options:
|
|
84
|
+
* insertion_cost: (default: 1)
|
|
85
|
+
* deletion_cost: number (default: 1)
|
|
86
|
+
* substitution_cost: number (default: 1)
|
|
87
|
+
* transposition_cost: number (default: 1)
|
|
88
|
+
* search: boolean (default: false)
|
|
89
|
+
* restricted: boolean (default: false)
|
|
90
|
+
*/
|
|
91
|
+
function DamerauLevenshteinDistance(source, target, options) {
|
|
92
|
+
var damLevOptions = _.extend(
|
|
93
|
+
{ transposition_cost: 1, restricted: false },
|
|
94
|
+
options || {},
|
|
95
|
+
{ damerau: true }
|
|
96
|
+
);
|
|
97
|
+
return levenshteinDistance(source, target, damLevOptions);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function LevenshteinDistance(source, target, options) {
|
|
101
|
+
var levOptions = _.extend({}, options || {}, { damerau: false });
|
|
102
|
+
return levenshteinDistance(source, target, levOptions);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
function levenshteinDistance (source, target, options) {
|
|
34
107
|
if(isNaN(options.insertion_cost)) options.insertion_cost = 1;
|
|
35
108
|
if(isNaN(options.deletion_cost)) options.deletion_cost = 1;
|
|
36
109
|
if(isNaN(options.substitution_cost)) options.substitution_cost = 1;
|
|
37
110
|
|
|
111
|
+
if(typeof options.search !== 'boolean') options.search = false;
|
|
112
|
+
|
|
113
|
+
var isUnrestrictedDamerau = options.damerau && !options.restricted;
|
|
114
|
+
var isRestrictedDamerau = options.damerau && options.restricted;
|
|
115
|
+
|
|
116
|
+
if (isUnrestrictedDamerau) {
|
|
117
|
+
var lastRowMap = {};
|
|
118
|
+
}
|
|
119
|
+
|
|
38
120
|
var sourceLength = source.length;
|
|
39
121
|
var targetLength = target.length;
|
|
40
|
-
var distanceMatrix = [[0]];
|
|
122
|
+
var distanceMatrix = [[{cost: 0}]]; //the root, has no parent cell
|
|
41
123
|
|
|
42
124
|
for (var row = 1; row <= sourceLength; row++) {
|
|
43
125
|
distanceMatrix[row] = [];
|
|
44
|
-
distanceMatrix[row][0] = distanceMatrix[row-1][0] + options.deletion_cost;
|
|
126
|
+
distanceMatrix[row][0] = {cost: distanceMatrix[row-1][0].cost + options.deletion_cost, parentCell: {row: row-1, column: 0}};
|
|
45
127
|
}
|
|
46
128
|
|
|
47
129
|
for (var column = 1; column <= targetLength; column++) {
|
|
48
|
-
|
|
130
|
+
if (options.search) {
|
|
131
|
+
distanceMatrix[0][column] = {cost: 0};
|
|
132
|
+
} else {
|
|
133
|
+
distanceMatrix[0][column] = {cost: distanceMatrix[0][column-1].cost + options.insertion_cost, parentCell: {row: 0, column: column-1}};
|
|
134
|
+
}
|
|
49
135
|
}
|
|
50
136
|
|
|
51
137
|
for (var row = 1; row <= sourceLength; row++) {
|
|
138
|
+
if (isUnrestrictedDamerau) {
|
|
139
|
+
var lastColMatch = null;
|
|
140
|
+
}
|
|
52
141
|
for (var column = 1; column <= targetLength; column++) {
|
|
53
|
-
var costToInsert = distanceMatrix[row][column-1] + options.insertion_cost;
|
|
54
|
-
var costToDelete = distanceMatrix[row-1][column] + options.deletion_cost;
|
|
142
|
+
var costToInsert = distanceMatrix[row][column-1].cost + options.insertion_cost;
|
|
143
|
+
var costToDelete = distanceMatrix[row-1][column].cost + options.deletion_cost;
|
|
55
144
|
|
|
56
145
|
var sourceElement = source[row-1];
|
|
57
146
|
var targetElement = target[column-1];
|
|
58
|
-
var costToSubstitute = distanceMatrix[row-1][column-1];
|
|
147
|
+
var costToSubstitute = distanceMatrix[row-1][column-1].cost;
|
|
59
148
|
if (sourceElement !== targetElement) {
|
|
60
149
|
costToSubstitute = costToSubstitute + options.substitution_cost;
|
|
61
150
|
}
|
|
62
|
-
|
|
151
|
+
|
|
152
|
+
var possibleParents = [
|
|
153
|
+
{cost: costToInsert, coordinates: {row: row, column: column-1}},
|
|
154
|
+
{cost: costToDelete, coordinates: {row: row-1, column: column}},
|
|
155
|
+
{cost: costToSubstitute, coordinates: {row: row-1, column: column-1}}
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
// We can add damerau to the possibleParents if the current
|
|
159
|
+
// target-letter has been encountered in our lastRowMap,
|
|
160
|
+
// and if there exists a previous column in this row where the
|
|
161
|
+
// row & column letters matched
|
|
162
|
+
var canDamerau = isUnrestrictedDamerau
|
|
163
|
+
&& row > 1 && column > 1
|
|
164
|
+
&& lastColMatch
|
|
165
|
+
&& targetElement in lastRowMap;
|
|
166
|
+
|
|
167
|
+
if (canDamerau) {
|
|
168
|
+
var lastRowMatch = lastRowMap[targetElement];
|
|
169
|
+
var costBeforeTransposition =
|
|
170
|
+
distanceMatrix[lastRowMatch - 1][lastColMatch - 1].cost;
|
|
171
|
+
var costToTranspose = costBeforeTransposition
|
|
172
|
+
+ ((row - lastRowMatch - 1) * options.deletion_cost)
|
|
173
|
+
+ ((column - lastColMatch - 1) * options.insertion_cost)
|
|
174
|
+
+ options.transposition_cost;
|
|
175
|
+
possibleParents.push({
|
|
176
|
+
cost: costToTranspose,
|
|
177
|
+
coordinates: {
|
|
178
|
+
row: lastRowMatch - 1,
|
|
179
|
+
column: lastColMatch - 1,
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
// Source and target chars are 1-indexed in the distanceMatrix so previous
|
|
184
|
+
// source/target element is (col/row - 2)
|
|
185
|
+
var canDoRestrictedDamerau = isRestrictedDamerau
|
|
186
|
+
&& row > 1 && column > 1
|
|
187
|
+
&& sourceElement === target[column - 2]
|
|
188
|
+
&& source[row - 2] === targetElement;
|
|
189
|
+
|
|
190
|
+
if (canDoRestrictedDamerau) {
|
|
191
|
+
var costBeforeTransposition = distanceMatrix[row - 2][column - 2].cost;
|
|
192
|
+
possibleParents.push({
|
|
193
|
+
cost: costBeforeTransposition + options.transposition_cost,
|
|
194
|
+
coordinates: { row: row - 2, column: column - 2 },
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
var minCostParent = _.min(possibleParents, function(p) { return p.cost; });
|
|
199
|
+
|
|
200
|
+
distanceMatrix[row][column] = {cost: minCostParent.cost, parentCell: minCostParent.coordinates};
|
|
201
|
+
|
|
202
|
+
if (isUnrestrictedDamerau) {
|
|
203
|
+
lastRowMap[sourceElement] = row;
|
|
204
|
+
if (sourceElement === targetElement) {
|
|
205
|
+
lastColMatch = column;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
63
208
|
}
|
|
64
209
|
}
|
|
65
|
-
|
|
210
|
+
|
|
211
|
+
if (!options.search) {
|
|
212
|
+
return distanceMatrix[sourceLength][targetLength].cost;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return getMinCostSubstring(distanceMatrix, source, target);
|
|
66
216
|
}
|
|
67
217
|
|
|
68
|
-
module.exports =
|
|
218
|
+
module.exports = {
|
|
219
|
+
LevenshteinDistance: LevenshteinDistance,
|
|
220
|
+
DamerauLevenshteinDistance: DamerauLevenshteinDistance,
|
|
221
|
+
};
|