inferx 0.2.1 → 0.2.2
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.
- data/lib/inferx.rb +9 -4
- data/lib/inferx/adapter.rb +1 -1
- data/lib/inferx/categories.rb +14 -8
- data/lib/inferx/category.rb +27 -23
- data/lib/inferx/complementary/categories.rb +14 -0
- data/lib/inferx/complementary/category.rb +82 -0
- data/lib/inferx/version.rb +1 -1
- data/spec/inferx/adapter_spec.rb +3 -3
- data/spec/inferx/categories_spec.rb +44 -32
- data/spec/inferx/category_spec.rb +50 -30
- data/spec/inferx/complementary/categories_spec.rb +25 -0
- data/spec/inferx/complementary/category_spec.rb +113 -0
- data/spec/inferx_spec.rb +32 -14
- data/spec/spec_helper.rb +1 -1
- metadata +8 -2
data/lib/inferx.rb
CHANGED
@@ -2,16 +2,20 @@ require 'redis'
|
|
2
2
|
|
3
3
|
require 'inferx/version'
|
4
4
|
require 'inferx/categories'
|
5
|
+
require 'inferx/complementary/categories'
|
5
6
|
|
6
7
|
class Inferx
|
7
8
|
|
8
9
|
# @param [Hash] options other options are passed to Redis#initialize in
|
9
10
|
# {https://github.com/redis/redis-rb redis}
|
10
11
|
#
|
12
|
+
# @option options [Boolean] :complementary
|
11
13
|
# @option options [String] :namespace namespace of keys to be used to Redis
|
12
14
|
# @option options [Boolean] :manual whether manual save, defaults to false
|
13
15
|
def initialize(options = {})
|
14
|
-
@
|
16
|
+
@complementary = !!options[:complementary]
|
17
|
+
categories_class = @complementary ? Complementary::Categories : Categories
|
18
|
+
@categories = categories_class.new(Redis.new(options), options)
|
15
19
|
end
|
16
20
|
|
17
21
|
attr_reader :categories
|
@@ -31,7 +35,7 @@ class Inferx
|
|
31
35
|
# Get a score for each category according to a set of words.
|
32
36
|
#
|
33
37
|
# @param [Array<String>] words a set of words
|
34
|
-
# @return [Hash<
|
38
|
+
# @return [Hash<String, Float>] scores to key a category
|
35
39
|
#
|
36
40
|
# @see #score
|
37
41
|
def classifications(words)
|
@@ -42,12 +46,13 @@ class Inferx
|
|
42
46
|
# Classify words to any one category.
|
43
47
|
#
|
44
48
|
# @param [Array<String>] words a set of words
|
45
|
-
# @return [
|
49
|
+
# @return [String] most high-scoring category name
|
46
50
|
#
|
47
51
|
# @see #score
|
48
52
|
# @see #classifications
|
49
53
|
def classify(words)
|
50
|
-
|
54
|
+
method_name = @complementary ? :min_by : :max_by
|
55
|
+
category = classifications(words).__send__(method_name) { |score| score[1] }
|
51
56
|
category ? category[0] : nil
|
52
57
|
end
|
53
58
|
end
|
data/lib/inferx/adapter.rb
CHANGED
@@ -37,7 +37,7 @@ class Inferx
|
|
37
37
|
|
38
38
|
# Make the key for access to scores stored each by word.
|
39
39
|
#
|
40
|
-
# @param [
|
40
|
+
# @param [String] category_name a category name
|
41
41
|
# @return [String] the key
|
42
42
|
def make_category_key(category_name)
|
43
43
|
"#{categories_key}:#{category_name}"
|
data/lib/inferx/categories.rb
CHANGED
@@ -7,25 +7,25 @@ class Inferx
|
|
7
7
|
|
8
8
|
# Get all category names.
|
9
9
|
#
|
10
|
-
# @return [Array<
|
10
|
+
# @return [Array<String>] category names
|
11
11
|
def all
|
12
|
-
|
12
|
+
hkeys || []
|
13
13
|
end
|
14
14
|
|
15
15
|
# Get a category according the name.
|
16
16
|
#
|
17
|
-
# @param [
|
17
|
+
# @param [String] category_name a category name
|
18
18
|
# @return [Inferx::Category] a category
|
19
19
|
def get(category_name)
|
20
20
|
size = hget(category_name)
|
21
|
-
raise ArgumentError, "
|
22
|
-
|
21
|
+
raise ArgumentError, "#{category_name.inspect} is missing" unless size
|
22
|
+
spawn_category(category_name, size.to_i)
|
23
23
|
end
|
24
24
|
alias [] get
|
25
25
|
|
26
26
|
# Add categories.
|
27
27
|
#
|
28
|
-
# @param [Array<
|
28
|
+
# @param [Array<String>] category_names category names
|
29
29
|
def add(*category_names)
|
30
30
|
@redis.pipelined do
|
31
31
|
category_names.each { |category_name| hsetnx(category_name, 0) }
|
@@ -35,7 +35,7 @@ class Inferx
|
|
35
35
|
|
36
36
|
# Remove categories.
|
37
37
|
#
|
38
|
-
# @param [Array<
|
38
|
+
# @param [Array<String>] category_names category names
|
39
39
|
def remove(*category_names)
|
40
40
|
@redis.pipelined do
|
41
41
|
category_names.each { |category_name| hdel(category_name) }
|
@@ -50,8 +50,14 @@ class Inferx
|
|
50
50
|
# @yieldparam [Inferx::Category] category a category
|
51
51
|
def each
|
52
52
|
hgetall.each do |category_name, size|
|
53
|
-
yield
|
53
|
+
yield spawn_category(category_name, size.to_i)
|
54
54
|
end
|
55
55
|
end
|
56
|
+
|
57
|
+
protected
|
58
|
+
|
59
|
+
def spawn_category(*args)
|
60
|
+
spawn(Category, *args)
|
61
|
+
end
|
56
62
|
end
|
57
63
|
end
|
data/lib/inferx/category.rb
CHANGED
@@ -4,21 +4,21 @@ class Inferx
|
|
4
4
|
class Category < Adapter
|
5
5
|
|
6
6
|
# @param [Redis] redis an instance of Redis
|
7
|
-
# @param [
|
7
|
+
# @param [String] name a category name
|
8
8
|
# @param [Integer] size total of scores
|
9
9
|
# @param [Hash] options
|
10
10
|
# @option options [String] :namespace namespace of keys to be used to Redis
|
11
11
|
# @option options [Boolean] :manual whether manual save, defaults to false
|
12
12
|
def initialize(redis, name, size, options = {})
|
13
13
|
super(redis, options)
|
14
|
-
@name = name
|
14
|
+
@name = name.to_s
|
15
15
|
@size = size
|
16
16
|
end
|
17
17
|
|
18
18
|
# Get a category name.
|
19
19
|
#
|
20
20
|
# @attribute [r] name
|
21
|
-
# @return [
|
21
|
+
# @return [String] a category name
|
22
22
|
|
23
23
|
# Get total of scores.
|
24
24
|
#
|
@@ -57,18 +57,18 @@ class Inferx
|
|
57
57
|
#
|
58
58
|
# @param [Array<String>] words an array of words
|
59
59
|
def train(words)
|
60
|
+
return if words.empty?
|
61
|
+
|
62
|
+
increase = words.size
|
63
|
+
words = collect(words)
|
64
|
+
|
60
65
|
@redis.pipelined do
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
end
|
65
|
-
|
66
|
-
if increase > 0
|
67
|
-
hincrby(name, increase)
|
68
|
-
@redis.save unless manual?
|
69
|
-
@size += increase
|
70
|
-
end
|
66
|
+
words.each { |word, count| zincrby(count, word) }
|
67
|
+
hincrby(name, increase)
|
68
|
+
@redis.save unless manual?
|
71
69
|
end
|
70
|
+
|
71
|
+
@size += increase
|
72
72
|
end
|
73
73
|
|
74
74
|
# Prepare to enhance the training data. Use for high performance.
|
@@ -83,27 +83,31 @@ class Inferx
|
|
83
83
|
#
|
84
84
|
# @param [Array<String>] words an array of words
|
85
85
|
def untrain(words)
|
86
|
-
|
86
|
+
return if words.empty?
|
87
87
|
|
88
|
-
|
89
|
-
|
90
|
-
zincrby(-pair[1], pair[0])
|
91
|
-
count + pair[1]
|
92
|
-
end
|
88
|
+
decrease = words.size
|
89
|
+
words = collect(words)
|
93
90
|
|
91
|
+
scores = @redis.pipelined do
|
92
|
+
words.each { |word, count| zincrby(-count, word) }
|
94
93
|
zremrangebyscore('-inf', 0)
|
95
94
|
end
|
96
95
|
|
97
|
-
|
96
|
+
length = words.size
|
97
|
+
|
98
|
+
scores[0, length].each do |score|
|
98
99
|
score = score.to_i
|
99
100
|
decrease += score if score < 0
|
100
101
|
end
|
101
102
|
|
102
|
-
|
103
|
+
return unless decrease > 0
|
104
|
+
|
105
|
+
@redis.pipelined do
|
103
106
|
hincrby(name, -decrease)
|
104
107
|
@redis.save unless manual?
|
105
|
-
@size -= decrease
|
106
108
|
end
|
109
|
+
|
110
|
+
@size -= decrease
|
107
111
|
end
|
108
112
|
|
109
113
|
# Prepare to attenuate the training data giving words.
|
@@ -123,7 +127,7 @@ class Inferx
|
|
123
127
|
scores.map { |score| score ? score.to_i : nil }
|
124
128
|
end
|
125
129
|
|
126
|
-
|
130
|
+
protected
|
127
131
|
|
128
132
|
%w(zrevrange zscore zincrby zremrangebyscore).each do |command|
|
129
133
|
define_method(command) do |*args|
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'inferx/category'
|
2
|
+
|
3
|
+
class Inferx
|
4
|
+
module Complementary
|
5
|
+
class Category < Inferx::Category
|
6
|
+
|
7
|
+
# Enhance the training data of other categories giving words.
|
8
|
+
#
|
9
|
+
# @param [Array<String>] words an array of words
|
10
|
+
def train(words)
|
11
|
+
return if words.empty?
|
12
|
+
|
13
|
+
increase = words.size
|
14
|
+
words = collect(words)
|
15
|
+
|
16
|
+
category_names = get_other_category_names
|
17
|
+
return if category_names.empty?
|
18
|
+
|
19
|
+
@redis.pipelined do
|
20
|
+
category_names.each do |category_name|
|
21
|
+
category_key = make_category_key(category_name)
|
22
|
+
words.each { |word, count| @redis.zincrby(category_key, count, word) }
|
23
|
+
hincrby(category_name, increase)
|
24
|
+
end
|
25
|
+
|
26
|
+
@redis.save unless manual?
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Attenuate the training data of other categories giving words.
|
31
|
+
#
|
32
|
+
# @param [Array<String>] words an array of words
|
33
|
+
def untrain(words)
|
34
|
+
return if words.empty?
|
35
|
+
|
36
|
+
decrease = words.size
|
37
|
+
words = collect(words)
|
38
|
+
|
39
|
+
category_names = get_other_category_names
|
40
|
+
return if category_names.empty?
|
41
|
+
|
42
|
+
scores = @redis.pipelined do
|
43
|
+
category_names.each do |category_name|
|
44
|
+
category_key = make_category_key(category_name)
|
45
|
+
words.each { |word, count| @redis.zincrby(category_key, -count, word) }
|
46
|
+
@redis.zremrangebyscore(category_key, '-inf', 0)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
length = words.size
|
51
|
+
decreases_by_category = {}
|
52
|
+
|
53
|
+
category_names.each_with_index do |category_name, index|
|
54
|
+
decrease_by_category = decrease
|
55
|
+
|
56
|
+
scores[index * (length + 1), length].each do |score|
|
57
|
+
score = score.to_i
|
58
|
+
decrease_by_category += score if score < 0
|
59
|
+
end
|
60
|
+
|
61
|
+
decreases_by_category[category_name] = decrease_by_category if decrease_by_category > 0
|
62
|
+
end
|
63
|
+
|
64
|
+
return if decreases_by_category.empty?
|
65
|
+
|
66
|
+
@redis.pipelined do
|
67
|
+
decreases_by_category.each do |category_name, decrease|
|
68
|
+
hincrby(category_name, -decrease)
|
69
|
+
end
|
70
|
+
|
71
|
+
@redis.save unless manual?
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def get_other_category_names
|
78
|
+
hkeys.reject { |category_name| category_name == name }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/lib/inferx/version.rb
CHANGED
data/spec/inferx/adapter_spec.rb
CHANGED
@@ -65,13 +65,13 @@ end
|
|
65
65
|
describe Inferx::Adapter, '#make_category_key' do
|
66
66
|
it 'returns the key for access to scores stored each by word' do
|
67
67
|
adapter = described_class.new(redis_stub)
|
68
|
-
adapter.make_category_key(
|
68
|
+
adapter.make_category_key('red').should == 'inferx:categories:red'
|
69
69
|
end
|
70
70
|
|
71
71
|
context 'with namespace' do
|
72
72
|
it 'returns the key included the namespace' do
|
73
73
|
adapter = described_class.new(redis_stub, :namespace => 'example')
|
74
|
-
adapter.make_category_key(
|
74
|
+
adapter.make_category_key('red').should == 'inferx:example:categories:red'
|
75
75
|
end
|
76
76
|
end
|
77
77
|
end
|
@@ -86,7 +86,7 @@ describe Inferx::Adapter, '#spawn' do
|
|
86
86
|
|
87
87
|
it 'returns an instance of the class' do
|
88
88
|
adapter = described_class.new(redis_stub)
|
89
|
-
klass = stub.tap { |s| s.stub!(:new
|
89
|
+
klass = stub.tap { |s| s.stub!(:new => 'klass') }
|
90
90
|
adapter.spawn(klass).should == 'klass'
|
91
91
|
end
|
92
92
|
end
|
@@ -27,18 +27,9 @@ describe Inferx::Categories, '#all' do
|
|
27
27
|
categories.all
|
28
28
|
end
|
29
29
|
|
30
|
-
it 'returns the all categories as Symbol' do
|
31
|
-
redis = redis_stub do |s|
|
32
|
-
s.stub!(:hkeys).and_return(%w(red green blue))
|
33
|
-
end
|
34
|
-
|
35
|
-
categories = described_class.new(redis)
|
36
|
-
categories.all.should == [:red, :green, :blue]
|
37
|
-
end
|
38
|
-
|
39
30
|
it 'returns an empty array if the key is missing' do
|
40
31
|
redis = redis_stub do |s|
|
41
|
-
s.stub!(:hkeys
|
32
|
+
s.stub!(:hkeys => [])
|
42
33
|
end
|
43
34
|
|
44
35
|
categories = described_class.new(redis)
|
@@ -49,40 +40,40 @@ end
|
|
49
40
|
describe Inferx::Categories, '#get' do
|
50
41
|
it 'calls Redis#hget' do
|
51
42
|
redis = redis_stub do |s|
|
52
|
-
s.should_receive(:hget).with('inferx:categories',
|
43
|
+
s.should_receive(:hget).with('inferx:categories', 'red').and_return('2')
|
53
44
|
end
|
54
45
|
|
55
46
|
categories = described_class.new(redis)
|
56
|
-
categories.get(
|
47
|
+
categories.get('red')
|
57
48
|
end
|
58
49
|
|
59
50
|
it 'calles Inferx::Category.new with the instance of Redis, the category name and the options' do
|
60
51
|
redis = redis_stub do |s|
|
61
|
-
s.stub!(:hget
|
52
|
+
s.stub!(:hget => '2')
|
62
53
|
end
|
63
54
|
|
64
|
-
Inferx::Category.should_receive(:new).with(redis,
|
55
|
+
Inferx::Category.should_receive(:new).with(redis, 'red', 2, :namespace => 'example', :manual => true)
|
65
56
|
categories = described_class.new(redis, :namespace => 'example', :manual => true)
|
66
|
-
categories.get(
|
57
|
+
categories.get('red')
|
67
58
|
end
|
68
59
|
|
69
60
|
it 'returns an instance of Inferx::Category' do
|
70
61
|
redis = redis_stub do |s|
|
71
|
-
s.stub!(:hget
|
62
|
+
s.stub!(:hget => '2')
|
72
63
|
end
|
73
64
|
|
74
65
|
categories = described_class.new(redis)
|
75
|
-
categories.get(
|
66
|
+
categories.get('red').should be_an(Inferx::Category)
|
76
67
|
end
|
77
68
|
|
78
69
|
context 'with a missing category' do
|
79
70
|
it 'raises ArgumentError' do
|
80
71
|
redis = redis_stub do |s|
|
81
|
-
s.stub!(:hget
|
72
|
+
s.stub!(:hget => nil)
|
82
73
|
end
|
83
74
|
|
84
75
|
categories = described_class.new(redis)
|
85
|
-
lambda { categories.get(
|
76
|
+
lambda { categories.get('red') }.should raise_error(ArgumentError, /"red" is missing/)
|
86
77
|
end
|
87
78
|
end
|
88
79
|
end
|
@@ -90,23 +81,23 @@ end
|
|
90
81
|
describe Inferx::Categories, '#add' do
|
91
82
|
it 'calls Redis#hsetnx' do
|
92
83
|
redis = redis_stub do |s|
|
93
|
-
s.should_receive(:hsetnx).with('inferx:categories',
|
84
|
+
s.should_receive(:hsetnx).with('inferx:categories', 'red', 0)
|
94
85
|
s.should_receive(:save)
|
95
86
|
end
|
96
87
|
|
97
88
|
categories = described_class.new(redis)
|
98
|
-
categories.add(
|
89
|
+
categories.add('red')
|
99
90
|
end
|
100
91
|
|
101
92
|
it 'calls Redis#hsetnx according to the number of the categories' do
|
102
93
|
redis = redis_stub do |s|
|
103
|
-
s.should_receive(:hsetnx).with('inferx:categories',
|
104
|
-
s.should_receive(:hsetnx).with('inferx:categories',
|
94
|
+
s.should_receive(:hsetnx).with('inferx:categories', 'red', 0)
|
95
|
+
s.should_receive(:hsetnx).with('inferx:categories', 'green', 0)
|
105
96
|
s.should_receive(:save)
|
106
97
|
end
|
107
98
|
|
108
99
|
categories = described_class.new(redis)
|
109
|
-
categories.add(
|
100
|
+
categories.add('red', 'green')
|
110
101
|
end
|
111
102
|
|
112
103
|
context 'with manual save' do
|
@@ -117,7 +108,7 @@ describe Inferx::Categories, '#add' do
|
|
117
108
|
end
|
118
109
|
|
119
110
|
categories = described_class.new(redis, :manual => true)
|
120
|
-
categories.add(
|
111
|
+
categories.add('red')
|
121
112
|
end
|
122
113
|
end
|
123
114
|
end
|
@@ -125,25 +116,25 @@ end
|
|
125
116
|
describe Inferx::Categories, '#remove' do
|
126
117
|
it 'calls Redis#hdel and Redis#del' do
|
127
118
|
redis = redis_stub do |s|
|
128
|
-
s.should_receive(:hdel).with('inferx:categories',
|
119
|
+
s.should_receive(:hdel).with('inferx:categories', 'red')
|
129
120
|
s.should_receive(:del).with('inferx:categories:red')
|
130
121
|
s.should_receive(:save)
|
131
122
|
end
|
132
123
|
|
133
124
|
categories = described_class.new(redis)
|
134
|
-
categories.remove(
|
125
|
+
categories.remove('red')
|
135
126
|
end
|
136
127
|
|
137
128
|
it 'calls Redis#hdel according to the number of the categories' do
|
138
129
|
redis = redis_stub do |s|
|
139
|
-
s.should_receive(:hdel).with('inferx:categories',
|
140
|
-
s.should_receive(:hdel).with('inferx:categories',
|
130
|
+
s.should_receive(:hdel).with('inferx:categories', 'red')
|
131
|
+
s.should_receive(:hdel).with('inferx:categories', 'green')
|
141
132
|
s.should_receive(:del).with('inferx:categories:red', 'inferx:categories:green')
|
142
133
|
s.should_receive(:save)
|
143
134
|
end
|
144
135
|
|
145
136
|
categories = described_class.new(redis)
|
146
|
-
categories.remove(
|
137
|
+
categories.remove('red', 'green')
|
147
138
|
end
|
148
139
|
|
149
140
|
context 'with manual save' do
|
@@ -155,7 +146,7 @@ describe Inferx::Categories, '#remove' do
|
|
155
146
|
end
|
156
147
|
|
157
148
|
categories = described_class.new(redis, :manual => true)
|
158
|
-
categories.remove(
|
149
|
+
categories.remove('red')
|
159
150
|
end
|
160
151
|
end
|
161
152
|
end
|
@@ -163,7 +154,11 @@ end
|
|
163
154
|
describe Inferx::Categories, '#each' do
|
164
155
|
before do
|
165
156
|
@redis = redis_stub do |s|
|
166
|
-
s.stub!(:hgetall
|
157
|
+
s.stub!(:hgetall => {
|
158
|
+
'red' => 2,
|
159
|
+
'green' => 3,
|
160
|
+
'blue' => 1
|
161
|
+
})
|
167
162
|
end
|
168
163
|
end
|
169
164
|
|
@@ -179,3 +174,20 @@ describe Inferx::Categories, '#each' do
|
|
179
174
|
n.should == 3
|
180
175
|
end
|
181
176
|
end
|
177
|
+
|
178
|
+
describe Inferx::Categories, '#spawn_category' do
|
179
|
+
before do
|
180
|
+
@categories = described_class.new(redis_stub).tap do |s|
|
181
|
+
s.stub!(:spawn => 'category')
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'calls #spawn with Inferx::Category and the arguments' do
|
186
|
+
@categories.should_receive(:spawn).with(Inferx::Category, 'arg1', 'arg2')
|
187
|
+
@categories.__send__(:spawn_category, 'arg1', 'arg2')
|
188
|
+
end
|
189
|
+
|
190
|
+
it 'returns the return value from #spawn' do
|
191
|
+
@categories.__send__(:spawn_category, 'arg1', 'arg2').should == 'category'
|
192
|
+
end
|
193
|
+
end
|
@@ -4,19 +4,19 @@ require 'inferx/category'
|
|
4
4
|
describe Inferx::Category, '#initialize' do
|
5
5
|
it 'calls Inferx::Adapter#initialize' do
|
6
6
|
redis = redis_stub
|
7
|
-
category = described_class.new(redis,
|
7
|
+
category = described_class.new(redis, 'red', 2, :namespace => 'example', :manual => true)
|
8
8
|
category.instance_eval { @redis }.should == redis
|
9
9
|
category.instance_eval { @namespace }.should == 'example'
|
10
10
|
category.should be_manual
|
11
11
|
end
|
12
12
|
|
13
13
|
it 'sets the category name to the name attribute' do
|
14
|
-
category = described_class.new(redis_stub,
|
15
|
-
category.name.should ==
|
14
|
+
category = described_class.new(redis_stub, 'red', 2)
|
15
|
+
category.name.should == 'red'
|
16
16
|
end
|
17
17
|
|
18
18
|
it 'sets the size to the size attribute' do
|
19
|
-
category = described_class.new(redis_stub,
|
19
|
+
category = described_class.new(redis_stub, 'red', 2)
|
20
20
|
category.size.should == 2
|
21
21
|
end
|
22
22
|
end
|
@@ -27,7 +27,7 @@ describe Inferx::Category, '#all' do
|
|
27
27
|
s.should_receive(:zrevrange).with('inferx:categories:red', 0, -1, :withscores => true).and_return([])
|
28
28
|
end
|
29
29
|
|
30
|
-
category = described_class.new(redis,
|
30
|
+
category = described_class.new(redis, 'red', 2)
|
31
31
|
category.all
|
32
32
|
end
|
33
33
|
|
@@ -36,7 +36,7 @@ describe Inferx::Category, '#all' do
|
|
36
36
|
s.stub!(:zrevrange).with('inferx:categories:red', 0, -1, :withscores => true).and_return(%w(apple 2 strawberry 3))
|
37
37
|
end
|
38
38
|
|
39
|
-
category = described_class.new(redis,
|
39
|
+
category = described_class.new(redis, 'red', 2)
|
40
40
|
category.all.should == {'apple' => 2, 'strawberry' => 3}
|
41
41
|
end
|
42
42
|
end
|
@@ -47,7 +47,7 @@ describe Inferx::Category, '#get' do
|
|
47
47
|
s.should_receive(:zscore).with('inferx:categories:red', 'apple')
|
48
48
|
end
|
49
49
|
|
50
|
-
category = described_class.new(redis,
|
50
|
+
category = described_class.new(redis, 'red', 2)
|
51
51
|
category.get('apple')
|
52
52
|
end
|
53
53
|
|
@@ -56,7 +56,7 @@ describe Inferx::Category, '#get' do
|
|
56
56
|
s.stub!(:zscore).with('inferx:categories:red', 'apple').and_return('1')
|
57
57
|
end
|
58
58
|
|
59
|
-
category = described_class.new(redis,
|
59
|
+
category = described_class.new(redis, 'red', 2)
|
60
60
|
category.get('apple').should == 1
|
61
61
|
end
|
62
62
|
|
@@ -66,7 +66,7 @@ describe Inferx::Category, '#get' do
|
|
66
66
|
s.stub!(:zscore).with('inferx:categories:red', 'strawberry').and_return(nil)
|
67
67
|
end
|
68
68
|
|
69
|
-
category = described_class.new(redis,
|
69
|
+
category = described_class.new(redis, 'red', 2)
|
70
70
|
category.get('strawberry').should be_nil
|
71
71
|
end
|
72
72
|
end
|
@@ -77,11 +77,11 @@ describe Inferx::Category, '#train' do
|
|
77
77
|
redis = redis_stub do |s|
|
78
78
|
s.should_receive(:zincrby).with('inferx:categories:red', 2, 'apple')
|
79
79
|
s.should_receive(:zincrby).with('inferx:categories:red', 3, 'strawberry')
|
80
|
-
s.should_receive(:hincrby).with('inferx:categories',
|
80
|
+
s.should_receive(:hincrby).with('inferx:categories', 'red', 5)
|
81
81
|
s.should_receive(:save)
|
82
82
|
end
|
83
83
|
|
84
|
-
category = described_class.new(redis,
|
84
|
+
category = described_class.new(redis, 'red', 2)
|
85
85
|
category.train(%w(apple strawberry apple strawberry strawberry))
|
86
86
|
end
|
87
87
|
|
@@ -91,7 +91,7 @@ describe Inferx::Category, '#train' do
|
|
91
91
|
s.stub!(:hincrby)
|
92
92
|
end
|
93
93
|
|
94
|
-
category = described_class.new(redis,
|
94
|
+
category = described_class.new(redis, 'red', 2)
|
95
95
|
category.train(%w(apple strawberry apple strawberry strawberry))
|
96
96
|
category.size.should == 7
|
97
97
|
end
|
@@ -103,8 +103,8 @@ describe Inferx::Category, '#train' do
|
|
103
103
|
s.should_not_receive(:save)
|
104
104
|
end
|
105
105
|
|
106
|
-
category = described_class.new(redis,
|
107
|
-
category.train(
|
106
|
+
category = described_class.new(redis, 'red', 2)
|
107
|
+
category.train([])
|
108
108
|
end
|
109
109
|
end
|
110
110
|
|
@@ -116,7 +116,7 @@ describe Inferx::Category, '#train' do
|
|
116
116
|
s.should_not_receive(:save)
|
117
117
|
end
|
118
118
|
|
119
|
-
category = described_class.new(redis,
|
119
|
+
category = described_class.new(redis, 'red', 2, :manual => true)
|
120
120
|
category.train(%w(apple strawberry apple strawberry strawberry))
|
121
121
|
end
|
122
122
|
end
|
@@ -124,7 +124,7 @@ end
|
|
124
124
|
|
125
125
|
describe Inferx::Category, '#ready_to_train' do
|
126
126
|
it 'calls #train with the words to train block' do
|
127
|
-
category = described_class.new(redis_stub,
|
127
|
+
category = described_class.new(redis_stub, 'red', 2)
|
128
128
|
category.should_receive(:train).with(%w(word1 word2 word3))
|
129
129
|
|
130
130
|
category.ready_to_train do |train|
|
@@ -138,39 +138,54 @@ end
|
|
138
138
|
describe Inferx::Category, '#untrain' do
|
139
139
|
it 'calls Redis#zincrby, Redis#zremrangebyscore and Redis#hincrby' do
|
140
140
|
redis = redis_stub do |s|
|
141
|
+
s.stub!(:pipelined).and_return do |&block|
|
142
|
+
block.call
|
143
|
+
%w(3 -2 1)
|
144
|
+
end
|
145
|
+
|
141
146
|
s.should_receive(:zincrby).with('inferx:categories:red', -2, 'apple')
|
142
147
|
s.should_receive(:zincrby).with('inferx:categories:red', -3, 'strawberry')
|
143
|
-
s.should_receive(:zremrangebyscore).with('inferx:categories:red', '-inf', 0)
|
144
|
-
s.should_receive(:hincrby).with('inferx:categories',
|
148
|
+
s.should_receive(:zremrangebyscore).with('inferx:categories:red', '-inf', 0)
|
149
|
+
s.should_receive(:hincrby).with('inferx:categories', 'red', -3)
|
145
150
|
s.should_receive(:save)
|
146
151
|
end
|
147
152
|
|
148
|
-
category = described_class.new(redis,
|
153
|
+
category = described_class.new(redis, 'red', 7)
|
149
154
|
category.untrain(%w(apple strawberry apple strawberry strawberry))
|
150
155
|
end
|
151
156
|
|
152
157
|
it 'decreases the size attribute' do
|
153
158
|
redis = redis_stub do |s|
|
159
|
+
s.stub!(:pipelined).and_return do |&block|
|
160
|
+
block.call
|
161
|
+
%w(3 -2 1)
|
162
|
+
end
|
163
|
+
|
154
164
|
s.stub!(:zincrby)
|
155
|
-
s.stub!(:zremrangebyscore)
|
165
|
+
s.stub!(:zremrangebyscore)
|
156
166
|
s.stub!(:hincrby)
|
157
167
|
end
|
158
168
|
|
159
|
-
category = described_class.new(redis,
|
169
|
+
category = described_class.new(redis, 'red', 7)
|
160
170
|
category.untrain(%w(apple strawberry apple strawberry strawberry))
|
161
171
|
category.size.should == 4
|
162
172
|
end
|
163
173
|
|
164
174
|
context 'with no update' do
|
165
|
-
it 'does not call Redis#hincrby' do
|
175
|
+
it 'does not call Redis#zremrangebyscore and Redis#hincrby' do
|
166
176
|
redis = redis_stub do |s|
|
177
|
+
s.stub!(:pipelined).and_return do |&block|
|
178
|
+
block.call
|
179
|
+
%w(-2 -3 2)
|
180
|
+
end
|
181
|
+
|
167
182
|
s.stub!(:zincrby)
|
168
|
-
s.stub!(:zremrangebyscore)
|
183
|
+
s.stub!(:zremrangebyscore)
|
169
184
|
s.should_not_receive(:hincrby)
|
170
185
|
s.should_not_receive(:save)
|
171
186
|
end
|
172
187
|
|
173
|
-
category = described_class.new(redis,
|
188
|
+
category = described_class.new(redis, 'red', 7)
|
174
189
|
category.untrain(%w(apple strawberry apple strawberry strawberry))
|
175
190
|
end
|
176
191
|
end
|
@@ -178,13 +193,18 @@ describe Inferx::Category, '#untrain' do
|
|
178
193
|
context 'with manual save' do
|
179
194
|
it 'does not call Redis#save' do
|
180
195
|
redis = redis_stub do |s|
|
196
|
+
s.stub!(:pipelined).and_return do |&block|
|
197
|
+
block.call
|
198
|
+
%w(3 -2 1)
|
199
|
+
end
|
200
|
+
|
181
201
|
s.stub!(:zincrby)
|
182
|
-
s.stub!(:zremrangebyscore)
|
202
|
+
s.stub!(:zremrangebyscore)
|
183
203
|
s.stub!(:hincrby)
|
184
204
|
s.should_not_receive(:save)
|
185
205
|
end
|
186
206
|
|
187
|
-
category = described_class.new(redis,
|
207
|
+
category = described_class.new(redis, 'red', 7, :manual => true)
|
188
208
|
category.untrain(%w(apple strawberry apple strawberry strawberry))
|
189
209
|
end
|
190
210
|
end
|
@@ -192,7 +212,7 @@ end
|
|
192
212
|
|
193
213
|
describe Inferx::Category, '#ready_to_untrain' do
|
194
214
|
it 'calls #untrain with the words to untrain block' do
|
195
|
-
category = described_class.new(redis_stub,
|
215
|
+
category = described_class.new(redis_stub, 'red', 2)
|
196
216
|
category.should_receive(:untrain).with(%w(word1 word2 word3))
|
197
217
|
|
198
218
|
category.ready_to_untrain do |untrain|
|
@@ -210,16 +230,16 @@ describe Inferx::Category, '#scores' do
|
|
210
230
|
s.should_receive(:zscore).with('inferx:categories:red', 'strawberry')
|
211
231
|
end
|
212
232
|
|
213
|
-
category = described_class.new(redis,
|
233
|
+
category = described_class.new(redis, 'red', 2)
|
214
234
|
category.scores(%w(apple strawberry))
|
215
235
|
end
|
216
236
|
|
217
237
|
it 'returns the scores' do
|
218
238
|
redis = redis_stub do |s|
|
219
|
-
s.stub!(:pipelined
|
239
|
+
s.stub!(:pipelined => %w(2 3))
|
220
240
|
end
|
221
241
|
|
222
|
-
category = described_class.new(redis,
|
242
|
+
category = described_class.new(redis, 'red', 2)
|
223
243
|
scores = category.scores(%w(apple strawberry))
|
224
244
|
scores.should == [2, 3]
|
225
245
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'inferx/complementary/categories'
|
3
|
+
|
4
|
+
describe Inferx::Complementary::Categories do
|
5
|
+
it 'inherits Inferx::Categories' do
|
6
|
+
described_class.superclass.should == Inferx::Categories
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe Inferx::Complementary::Categories, '#spawn_category' do
|
11
|
+
before do
|
12
|
+
@categories = described_class.new(redis_stub).tap do |s|
|
13
|
+
s.stub!(:spawn => 'category')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'calls #spawn with Inferx::Complementary::Category, the categories and the arguments' do
|
18
|
+
@categories.should_receive(:spawn).with(Inferx::Complementary::Category, 'arg1', 'arg2')
|
19
|
+
@categories.__send__(:spawn_category, 'arg1', 'arg2')
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'returns the return value from #spawn' do
|
23
|
+
@categories.__send__(:spawn_category, 'arg1', 'arg2').should == 'category'
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'inferx/complementary/category'
|
2
|
+
|
3
|
+
describe Inferx::Complementary::Category, '#train' do
|
4
|
+
it 'calls Redis#zincrby and Redis#hincrby for other categories' do
|
5
|
+
redis = redis_stub do |s|
|
6
|
+
s.stub!(:hkeys => %w(red green blue))
|
7
|
+
s.should_receive(:zincrby).with('inferx:categories:green', 2, 'apple')
|
8
|
+
s.should_receive(:zincrby).with('inferx:categories:green', 3, 'strawberry')
|
9
|
+
s.should_receive(:hincrby).with('inferx:categories', 'green', 5)
|
10
|
+
s.should_receive(:zincrby).with('inferx:categories:blue', 2, 'apple')
|
11
|
+
s.should_receive(:zincrby).with('inferx:categories:blue', 3, 'strawberry')
|
12
|
+
s.should_receive(:hincrby).with('inferx:categories', 'blue', 5)
|
13
|
+
s.should_receive(:save)
|
14
|
+
end
|
15
|
+
|
16
|
+
category = described_class.new(redis, 'red', 2)
|
17
|
+
category.train(%w(apple strawberry apple strawberry strawberry))
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'with no update' do
|
21
|
+
it 'does not call Redis#hincrby' do
|
22
|
+
redis = redis_stub do |s|
|
23
|
+
s.should_not_receive(:hincrby)
|
24
|
+
s.should_not_receive(:save)
|
25
|
+
end
|
26
|
+
|
27
|
+
category = described_class.new(redis, 'red', 2)
|
28
|
+
category.train([])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'with manual save' do
|
33
|
+
it 'does not call Redis#save' do
|
34
|
+
redis = redis_stub do |s|
|
35
|
+
s.stub!(:hkeys => %w(red green blue))
|
36
|
+
s.stub!(:zincrby)
|
37
|
+
s.stub!(:hincrby)
|
38
|
+
s.should_not_receive(:save)
|
39
|
+
end
|
40
|
+
|
41
|
+
category = described_class.new(redis, 'red', 2, :manual => true)
|
42
|
+
category.train(%w(apple strawberry apple strawberry strawberry))
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe Inferx::Complementary::Category, '#untrain' do
|
48
|
+
it 'calls Redis#zincrby, Redis#zremrangebyscore and Redis#hincrby for other categories' do
|
49
|
+
redis = redis_stub do |s|
|
50
|
+
s.stub!(:hkeys => %w(red green blue))
|
51
|
+
|
52
|
+
s.stub!(:pipelined).and_return do |&block|
|
53
|
+
block.call
|
54
|
+
%w(1 1 0 2 -1 1)
|
55
|
+
end
|
56
|
+
|
57
|
+
s.should_receive(:zincrby).with('inferx:categories:green', -2, 'apple')
|
58
|
+
s.should_receive(:zincrby).with('inferx:categories:green', -3, 'strawberry')
|
59
|
+
s.should_receive(:zremrangebyscore).with('inferx:categories:green', '-inf', 0)
|
60
|
+
s.should_receive(:zincrby).with('inferx:categories:blue', -2, 'apple')
|
61
|
+
s.should_receive(:zincrby).with('inferx:categories:blue', -3, 'strawberry')
|
62
|
+
s.should_receive(:zremrangebyscore).with('inferx:categories:blue', '-inf', 0)
|
63
|
+
s.should_receive(:hincrby).with('inferx:categories', 'green', -5)
|
64
|
+
s.should_receive(:hincrby).with('inferx:categories', 'blue', -4)
|
65
|
+
s.should_receive(:save)
|
66
|
+
end
|
67
|
+
|
68
|
+
category = described_class.new(redis, 'red', 2)
|
69
|
+
category.untrain(%w(apple strawberry apple strawberry strawberry))
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'with no update' do
|
73
|
+
it 'does not call Redis#zremrangebyscore and Redis#hincrby' do
|
74
|
+
redis = redis_stub do |s|
|
75
|
+
s.stub!(:hkeys => %w(red green blue))
|
76
|
+
|
77
|
+
s.stub!(:pipelined).and_return do |&block|
|
78
|
+
block.call
|
79
|
+
%w(-3 -2 2 -3 -2 2)
|
80
|
+
end
|
81
|
+
|
82
|
+
s.stub!(:zincrby)
|
83
|
+
s.stub!(:zremrangebyscore)
|
84
|
+
s.should_not_receive(:hincrby)
|
85
|
+
s.should_not_receive(:save)
|
86
|
+
end
|
87
|
+
|
88
|
+
category = described_class.new(redis, 'red', 2)
|
89
|
+
category.untrain([])
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context 'with manual save' do
|
94
|
+
it 'does not call Redis#save' do
|
95
|
+
redis = redis_stub do |s|
|
96
|
+
s.stub!(:hkeys => %w(red green blue))
|
97
|
+
|
98
|
+
s.stub!(:pipelined).and_return do |&block|
|
99
|
+
block.call
|
100
|
+
%w(1 1 0 2 -1 1)
|
101
|
+
end
|
102
|
+
|
103
|
+
s.stub!(:zincrby)
|
104
|
+
s.stub!(:zremrangebyscore)
|
105
|
+
s.stub!(:hincrby)
|
106
|
+
s.should_not_receive(:save)
|
107
|
+
end
|
108
|
+
|
109
|
+
category = described_class.new(redis, 'red', 2, :manual => true)
|
110
|
+
category.untrain(%w(apple strawberry apple strawberry strawberry))
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
data/spec/inferx_spec.rb
CHANGED
@@ -23,6 +23,14 @@ describe Inferx, '#initialize' do
|
|
23
23
|
inferx = described_class.new
|
24
24
|
inferx.categories.should be_an(Inferx::Categories)
|
25
25
|
end
|
26
|
+
|
27
|
+
context 'with :complementary option' do
|
28
|
+
it "sets an instance of #{described_class}::Complementary::Categories to the categories attribute" do
|
29
|
+
redis_stub
|
30
|
+
inferx = described_class.new(:complementary => true)
|
31
|
+
inferx.categories.should be_an(Inferx::Complementary::Categories)
|
32
|
+
end
|
33
|
+
end
|
26
34
|
end
|
27
35
|
|
28
36
|
describe Inferx, '#score' do
|
@@ -48,8 +56,7 @@ describe Inferx, '#score' do
|
|
48
56
|
words, size, scores = args
|
49
57
|
|
50
58
|
category = stub.tap do |s|
|
51
|
-
s.stub!(:size
|
52
|
-
s.stub!(:scores).and_return(scores)
|
59
|
+
s.stub!(:size => size, :scores => scores)
|
53
60
|
end
|
54
61
|
|
55
62
|
@inferx.score(category, words).should == expected
|
@@ -58,7 +65,7 @@ describe Inferx, '#score' do
|
|
58
65
|
|
59
66
|
it 'returns a negative infinity number if the category does not have words' do
|
60
67
|
category = stub.tap do |s|
|
61
|
-
s.stub!(:size
|
68
|
+
s.stub!(:size => 0)
|
62
69
|
end
|
63
70
|
|
64
71
|
score = @inferx.score(category, %w(apple))
|
@@ -68,8 +75,7 @@ describe Inferx, '#score' do
|
|
68
75
|
|
69
76
|
it 'returns 0.0 if the words are empty' do
|
70
77
|
category = stub.tap do |s|
|
71
|
-
s.stub!(:size
|
72
|
-
s.stub!(:scores).and_return([])
|
78
|
+
s.stub!(:size => 2, :scores => [])
|
73
79
|
end
|
74
80
|
|
75
81
|
score = @inferx.score(category, [])
|
@@ -80,8 +86,8 @@ end
|
|
80
86
|
|
81
87
|
describe Inferx, '#classifications' do
|
82
88
|
before do
|
83
|
-
categories =
|
84
|
-
stub.tap { |s| s.stub!(:name
|
89
|
+
categories = %w(red green blue).map do |category_name|
|
90
|
+
stub.tap { |s| s.stub!(:name => category_name) }
|
85
91
|
end
|
86
92
|
|
87
93
|
@inferx = described_class.new.tap do |s|
|
@@ -110,9 +116,9 @@ describe Inferx, '#classifications' do
|
|
110
116
|
|
111
117
|
it 'returns the scores to key the category name' do
|
112
118
|
@inferx.classifications(%w(apple)).should == {
|
113
|
-
|
114
|
-
|
115
|
-
|
119
|
+
'red' => 'score of red',
|
120
|
+
'green' => 'score of green',
|
121
|
+
'blue' => 'score of blue'
|
116
122
|
}
|
117
123
|
end
|
118
124
|
end
|
@@ -120,27 +126,39 @@ end
|
|
120
126
|
describe Inferx, '#classify' do
|
121
127
|
before do
|
122
128
|
@inferx = described_class.new.tap do |s|
|
123
|
-
s.stub!(:classifications
|
129
|
+
s.stub!(:classifications => {'red' => -2, 'green' => -1, 'blue' => -3})
|
124
130
|
end
|
125
131
|
end
|
126
132
|
|
127
133
|
it "calls #{described_class}#classifications" do
|
128
134
|
@inferx.tap do |m|
|
129
|
-
m.should_receive(:classifications).with(%w(apple)).and_return(
|
135
|
+
m.should_receive(:classifications).with(%w(apple)).and_return('red' => -2)
|
130
136
|
end
|
131
137
|
|
132
138
|
@inferx.classify(%w(apple))
|
133
139
|
end
|
134
140
|
|
135
141
|
it 'returns the most high-scoring category' do
|
136
|
-
@inferx.classify(%w(apple)).should ==
|
142
|
+
@inferx.classify(%w(apple)).should == 'green'
|
137
143
|
end
|
138
144
|
|
139
145
|
it 'returns nil if the categories is nothing' do
|
140
146
|
@inferx.tap do |s|
|
141
|
-
s.stub!(:classifications
|
147
|
+
s.stub!(:classifications => {})
|
142
148
|
end
|
143
149
|
|
144
150
|
@inferx.classify(%w(apple)).should be_nil
|
145
151
|
end
|
152
|
+
|
153
|
+
context 'when construct with :complementary option' do
|
154
|
+
before do
|
155
|
+
@inferx = described_class.new(:complementary => true).tap do |s|
|
156
|
+
s.stub!(:classifications => {'red' => -2, 'green' => -1, 'blue' => -3})
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'returns the most lower-scoring category' do
|
161
|
+
@inferx.classify(%w(apple)).should == 'blue'
|
162
|
+
end
|
163
|
+
end
|
146
164
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: inferx
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-10-
|
12
|
+
date: 2012-10-22 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: redis
|
@@ -61,10 +61,14 @@ files:
|
|
61
61
|
- lib/inferx/adapter.rb
|
62
62
|
- lib/inferx/categories.rb
|
63
63
|
- lib/inferx/category.rb
|
64
|
+
- lib/inferx/complementary/categories.rb
|
65
|
+
- lib/inferx/complementary/category.rb
|
64
66
|
- lib/inferx/version.rb
|
65
67
|
- spec/inferx/adapter_spec.rb
|
66
68
|
- spec/inferx/categories_spec.rb
|
67
69
|
- spec/inferx/category_spec.rb
|
70
|
+
- spec/inferx/complementary/categories_spec.rb
|
71
|
+
- spec/inferx/complementary/category_spec.rb
|
68
72
|
- spec/inferx_spec.rb
|
69
73
|
- spec/spec_helper.rb
|
70
74
|
homepage: ''
|
@@ -95,6 +99,8 @@ test_files:
|
|
95
99
|
- spec/inferx/adapter_spec.rb
|
96
100
|
- spec/inferx/categories_spec.rb
|
97
101
|
- spec/inferx/category_spec.rb
|
102
|
+
- spec/inferx/complementary/categories_spec.rb
|
103
|
+
- spec/inferx/complementary/category_spec.rb
|
98
104
|
- spec/inferx_spec.rb
|
99
105
|
- spec/spec_helper.rb
|
100
106
|
has_rdoc:
|