natural 7.0.1 → 7.0.3
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.
|
@@ -25,7 +25,7 @@ THE SOFTWARE.
|
|
|
25
25
|
const _ = require('underscore')
|
|
26
26
|
|
|
27
27
|
/*
|
|
28
|
-
Sentences
|
|
28
|
+
Sentences Analyzer Class
|
|
29
29
|
From http://www.writingcentre.uottawa.ca/hypergrammar/sntpurps.html
|
|
30
30
|
|
|
31
31
|
Take a POS input and analyse it for
|
|
@@ -44,148 +44,150 @@ const _ = require('underscore')
|
|
|
44
44
|
- Show Preposition Phrases
|
|
45
45
|
*/
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
class SentenceAnalyzer {
|
|
48
|
+
constructor (pos, callbackFunction) {
|
|
49
|
+
this.posObj = pos
|
|
50
|
+
this.senType = null
|
|
51
|
+
callbackFunction(this)
|
|
52
|
+
}
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
part (callbackFunction) {
|
|
55
|
+
const subject = []
|
|
56
|
+
const predicat = []
|
|
57
|
+
let verbFound = false
|
|
57
58
|
|
|
58
|
-
|
|
59
|
+
this.prepositionPhrases()
|
|
59
60
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
verbFound = true
|
|
64
|
-
} else {
|
|
65
|
-
// We need to Test for any EX before the VB
|
|
66
|
-
if (this.posObj.tags[i - 1].pos !== 'EX') {
|
|
61
|
+
for (let i = 0; i < this.posObj.tags.length; i++) {
|
|
62
|
+
if (this.posObj.tags[i].pos === 'VB') {
|
|
63
|
+
if (i === 0) {
|
|
67
64
|
verbFound = true
|
|
68
65
|
} else {
|
|
69
|
-
|
|
66
|
+
// We need to Test for any EX before the VB
|
|
67
|
+
if (this.posObj.tags[i - 1].pos !== 'EX') {
|
|
68
|
+
verbFound = true
|
|
69
|
+
} else {
|
|
70
|
+
predicat.push(this.posObj.tags[i].token)
|
|
71
|
+
}
|
|
70
72
|
}
|
|
71
73
|
}
|
|
72
|
-
}
|
|
73
74
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
// Add Pronoun Phrase (pp) Or Subject Phrase (sp)
|
|
76
|
+
if (!verbFound) {
|
|
77
|
+
if (this.posObj.tags[i].pp !== true) { this.posObj.tags[i].spos = 'SP' }
|
|
77
78
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
subject.push(this.posObj.tags[i].token)
|
|
80
|
+
} else {
|
|
81
|
+
if (this.posObj.tags[i].pp !== true) { this.posObj.tags[i].spos = 'PP' }
|
|
81
82
|
|
|
82
|
-
|
|
83
|
+
predicat.push(this.posObj.tags[i].token)
|
|
84
|
+
}
|
|
83
85
|
}
|
|
84
|
-
}
|
|
85
86
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
if (subject.length === 0) {
|
|
88
|
+
this.posObj.tags.push({ token: 'You', spos: 'SP', pos: 'PRP', added: true })
|
|
89
|
+
}
|
|
89
90
|
|
|
90
|
-
|
|
91
|
-
}
|
|
91
|
+
callbackFunction(this)
|
|
92
|
+
}
|
|
92
93
|
|
|
93
|
-
// Takes POS and removes IN to NN or NNS
|
|
94
|
-
// Adds a PP for each prepositionPhrases
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
// Takes POS and removes IN to NN or NNS
|
|
95
|
+
// Adds a PP for each prepositionPhrases
|
|
96
|
+
prepositionPhrases () {
|
|
97
|
+
let remove = false
|
|
97
98
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
for (let i = 0; i < this.posObj.tags.length; i++) {
|
|
100
|
+
if (this.posObj.tags[i].pos.match('IN')) {
|
|
101
|
+
remove = true
|
|
102
|
+
}
|
|
102
103
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
if (remove) {
|
|
105
|
+
this.posObj.tags[i].pp = true
|
|
106
|
+
}
|
|
106
107
|
|
|
107
|
-
|
|
108
|
-
|
|
108
|
+
if (this.posObj.tags[i].pos.match('NN')) {
|
|
109
|
+
remove = false
|
|
110
|
+
}
|
|
109
111
|
}
|
|
110
112
|
}
|
|
111
|
-
}
|
|
112
113
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
114
|
+
subjectToString () {
|
|
115
|
+
return this.posObj.tags.map(function (t) {
|
|
116
|
+
if (t.spos === 'SP' || t.spos === 'S') {
|
|
117
|
+
return t.token
|
|
118
|
+
} else return null
|
|
119
|
+
}).join(' ')
|
|
120
|
+
}
|
|
120
121
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
122
|
+
predicateToString () {
|
|
123
|
+
return this.posObj.tags.map(function (t) {
|
|
124
|
+
if (t.spos === 'PP' || t.spos === 'P') {
|
|
125
|
+
return t.token
|
|
126
|
+
} else return null
|
|
127
|
+
}).join(' ')
|
|
128
|
+
}
|
|
128
129
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
implicitYou () {
|
|
131
|
+
for (let i = 0; i < this.posObj.tags.length; i++) {
|
|
132
|
+
if (this.posObj.tags[i].added) {
|
|
133
|
+
return true
|
|
134
|
+
}
|
|
133
135
|
}
|
|
136
|
+
|
|
137
|
+
return false
|
|
134
138
|
}
|
|
135
139
|
|
|
136
|
-
|
|
137
|
-
}
|
|
140
|
+
toString () {
|
|
141
|
+
return this.posObj.tags.map(function (t) { return t.token }).join(' ')
|
|
142
|
+
}
|
|
138
143
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
144
|
+
// This is quick and incomplete.
|
|
145
|
+
type (cbf) {
|
|
146
|
+
const callbackFunction = cbf || false
|
|
142
147
|
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
} else if (_.contains(['PRP'], lastElement.pos)) {
|
|
163
|
-
// Sentences that end in a Personal pronoun are most likely questions
|
|
164
|
-
// eg. We should run away, should we [?]
|
|
165
|
-
// eg. You want to see that again, do you [?]
|
|
166
|
-
this.senType = 'INTERROGATIVE'
|
|
167
|
-
} else {
|
|
168
|
-
this.senType = 'UNKNOWN'
|
|
169
|
-
}
|
|
170
|
-
} else {
|
|
171
|
-
switch (lastElement.token) {
|
|
172
|
-
case '?':
|
|
148
|
+
// Check for implicit you before popping a tag.
|
|
149
|
+
const implicitYou = this.implicitYou()
|
|
150
|
+
|
|
151
|
+
// FIXME - punct seems useless
|
|
152
|
+
let lastElement = this.posObj.punct()
|
|
153
|
+
// console.log(lastElement)
|
|
154
|
+
lastElement = (lastElement.length !== 0) ? lastElement.pop() : this.posObj.tags.pop()
|
|
155
|
+
// console.log(lastElement)
|
|
156
|
+
|
|
157
|
+
if (lastElement.pos !== '.') {
|
|
158
|
+
if (implicitYou) {
|
|
159
|
+
this.senType = 'COMMAND'
|
|
160
|
+
} else if (_.contains(['WDT', 'WP', 'WP$', 'WRB'], this.posObj.tags[0].pos)) {
|
|
161
|
+
// Sentences that start with: who, what where when why and how, then they are questions
|
|
162
|
+
this.senType = 'INTERROGATIVE'
|
|
163
|
+
} else if (_.contains(['PRP'], lastElement.pos)) {
|
|
164
|
+
// Sentences that end in a Personal pronoun are most likely questions
|
|
165
|
+
// eg. We should run away, should we [?]
|
|
166
|
+
// eg. You want to see that again, do you [?]
|
|
173
167
|
this.senType = 'INTERROGATIVE'
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
168
|
+
} else {
|
|
169
|
+
this.senType = 'UNKNOWN'
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
switch (lastElement.token) {
|
|
173
|
+
case '?':
|
|
174
|
+
this.senType = 'INTERROGATIVE'
|
|
175
|
+
break
|
|
176
|
+
case '!':
|
|
177
|
+
this.senType = (implicitYou) ? 'COMMAND' : 'EXCLAMATORY'
|
|
178
|
+
break
|
|
179
|
+
case '.':
|
|
180
|
+
this.senType = (implicitYou) ? 'COMMAND' : 'DECLARATIVE'
|
|
181
|
+
break
|
|
182
|
+
}
|
|
181
183
|
}
|
|
182
|
-
}
|
|
183
184
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
185
|
+
if (callbackFunction && _.isFunction(callbackFunction)) {
|
|
186
|
+
callbackFunction(this)
|
|
187
|
+
} else {
|
|
188
|
+
return this.senType
|
|
189
|
+
}
|
|
188
190
|
}
|
|
189
191
|
}
|
|
190
192
|
|
|
191
|
-
module.exports =
|
|
193
|
+
module.exports = SentenceAnalyzer
|
|
@@ -27,79 +27,81 @@ const Trie = require('../trie/trie')
|
|
|
27
27
|
// 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
|
|
28
28
|
|
|
29
29
|
// 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)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
this.word2frequency[wordlist[i]]
|
|
30
|
+
class Spellcheck {
|
|
31
|
+
constructor (wordlist) {
|
|
32
|
+
this.trie = new Trie()
|
|
33
|
+
this.trie.addStrings(wordlist)
|
|
34
|
+
this.word2frequency = {}
|
|
35
|
+
for (const i in wordlist) {
|
|
36
|
+
if (!this.word2frequency[wordlist[i]]) {
|
|
37
|
+
this.word2frequency[wordlist[i]] = 0
|
|
38
|
+
}
|
|
39
|
+
this.word2frequency[wordlist[i]]++
|
|
37
40
|
}
|
|
38
|
-
this.word2frequency[wordlist[i]]++
|
|
39
41
|
}
|
|
40
|
-
}
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
43
|
+
isCorrect (word) {
|
|
44
|
+
return this.trie.contains(word)
|
|
45
|
+
}
|
|
45
46
|
|
|
46
|
-
// Returns a list of suggested corrections, from highest to lowest probability
|
|
47
|
-
// maxDistance is the maximum edit distance
|
|
48
|
-
// 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.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
47
|
+
// Returns a list of suggested corrections, from highest to lowest probability
|
|
48
|
+
// maxDistance is the maximum edit distance
|
|
49
|
+
// 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.
|
|
50
|
+
getCorrections (word, maxDistance) {
|
|
51
|
+
const self = this
|
|
52
|
+
if (!maxDistance) maxDistance = 1
|
|
53
|
+
let edits = this.editsWithMaxDistance(word, maxDistance)
|
|
54
|
+
edits = edits.slice(1, edits.length)
|
|
55
|
+
edits = edits.map(function (editList) {
|
|
56
|
+
return editList.filter(function (word) { return self.isCorrect(word) })
|
|
57
|
+
.map(function (word) { return [word, self.word2frequency[word]] })
|
|
58
|
+
.sort(function (a, b) { return a[1] > b[1] ? -1 : 1 })
|
|
59
|
+
.map(function (wordscore) { return wordscore[0] })
|
|
60
|
+
})
|
|
61
|
+
let flattened = []
|
|
62
|
+
for (const i in edits) {
|
|
63
|
+
if (edits[i].length) {
|
|
64
|
+
flattened = flattened.concat(edits[i])
|
|
65
|
+
}
|
|
64
66
|
}
|
|
67
|
+
return flattened.filter(function (v, i, a) {
|
|
68
|
+
return a.indexOf(v) === i
|
|
69
|
+
})
|
|
65
70
|
}
|
|
66
|
-
return flattened.filter(function (v, i, a) {
|
|
67
|
-
return a.indexOf(v) === i
|
|
68
|
-
})
|
|
69
|
-
}
|
|
70
71
|
|
|
71
|
-
// Returns all edits that are 1 edit-distance away from the input word
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
72
|
+
// Returns all edits that are 1 edit-distance away from the input word
|
|
73
|
+
edits (word) {
|
|
74
|
+
const alphabet = 'abcdefghijklmnopqrstuvwxyz'
|
|
75
|
+
let edits = []
|
|
76
|
+
for (let i = 0; i < word.length + 1; i++) {
|
|
77
|
+
if (i > 0) edits.push(word.slice(0, i - 1) + word.slice(i, word.length)) // deletes
|
|
78
|
+
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
|
|
79
|
+
for (let k = 0; k < alphabet.length; k++) {
|
|
80
|
+
if (i > 0) edits.push(word.slice(0, i - 1) + alphabet[k] + word.slice(i, word.length)) // replaces
|
|
81
|
+
edits.push(word.slice(0, i) + alphabet[k] + word.slice(i, word.length)) // inserts
|
|
82
|
+
}
|
|
81
83
|
}
|
|
84
|
+
// Deduplicate edits
|
|
85
|
+
edits = edits.filter(function (v, i, a) { return a.indexOf(v) === i })
|
|
86
|
+
return edits
|
|
82
87
|
}
|
|
83
|
-
// Deduplicate edits
|
|
84
|
-
edits = edits.filter(function (v, i, a) { return a.indexOf(v) === i })
|
|
85
|
-
return edits
|
|
86
|
-
}
|
|
87
88
|
|
|
88
|
-
// Returns all edits that are up to "distance" edit distance away from the input word
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
89
|
+
// Returns all edits that are up to "distance" edit distance away from the input word
|
|
90
|
+
editsWithMaxDistance (word, distance) {
|
|
91
|
+
return this.editsWithMaxDistanceHelper(distance, [[word]])
|
|
92
|
+
}
|
|
92
93
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
94
|
+
editsWithMaxDistanceHelper (distanceCounter, distance2edits) {
|
|
95
|
+
if (distanceCounter === 0) return distance2edits
|
|
96
|
+
const currentDepth = distance2edits.length - 1
|
|
97
|
+
const words = distance2edits[currentDepth]
|
|
98
|
+
// const edits = this.edits(words[0])
|
|
99
|
+
distance2edits[currentDepth + 1] = []
|
|
100
|
+
for (const i in words) {
|
|
101
|
+
distance2edits[currentDepth + 1] = distance2edits[currentDepth + 1].concat(this.edits(words[i]))
|
|
102
|
+
}
|
|
103
|
+
return this.editsWithMaxDistanceHelper(distanceCounter - 1, distance2edits)
|
|
101
104
|
}
|
|
102
|
-
return this.editsWithMaxDistanceHelper(distanceCounter - 1, distance2edits)
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
module.exports = Spellcheck
|
package/lib/natural/util/bag.js
CHANGED
|
@@ -22,31 +22,33 @@ THE SOFTWARE.
|
|
|
22
22
|
|
|
23
23
|
'use strict'
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
25
|
+
class Bag {
|
|
26
|
+
constructor () {
|
|
27
|
+
this.dictionary = []
|
|
28
|
+
this.nElement = 0
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
add (element) {
|
|
32
|
+
this.dictionary.push(element)
|
|
33
|
+
this.nElement++
|
|
34
|
+
return this
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
isEmpty () {
|
|
38
|
+
return this.nElement > 0
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
contains (item) {
|
|
42
|
+
return this.dictionary.indexOf(item) >= 0
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* unpack the bag , and get all items
|
|
47
|
+
*/
|
|
48
|
+
unpack () {
|
|
49
|
+
// return a copy is better than original
|
|
50
|
+
return this.dictionary.slice()
|
|
51
|
+
}
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
module.exports = Bag
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "natural",
|
|
3
3
|
"description": "General natural language (tokenizing, stemming (English, Russian, Spanish), part-of-speech tagging, sentiment analysis, classification, inflection, phonetics, tfidf, WordNet, jaro-winkler, Levenshtein distance, Dice's Coefficient) facilities for node.",
|
|
4
|
-
"version": "7.0.
|
|
4
|
+
"version": "7.0.3",
|
|
5
5
|
"homepage": "https://github.com/NaturalNode/natural",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
"node": ">=0.4.10"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@types/jasmine": "^5.1.4",
|
|
15
14
|
"afinn-165": "^1.0.2",
|
|
16
15
|
"afinn-165-financialmarketnews": "^3.0.0",
|
|
17
16
|
"apparatus": "^0.0.10",
|
|
@@ -28,6 +27,7 @@
|
|
|
28
27
|
"wordnet-db": "^3.1.11"
|
|
29
28
|
},
|
|
30
29
|
"devDependencies": {
|
|
30
|
+
"@types/jasmine": "^5.1.4",
|
|
31
31
|
"@types/node": "^18.11.18",
|
|
32
32
|
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
|
33
33
|
"browserfs": "^1.4.3",
|