inferx 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
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
- @categories = Categories.new(Redis.new(options), options)
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<Symbol, Float>] scores to key a category
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 [Symbol] most high-scoring category name
49
+ # @return [String] most high-scoring category name
46
50
  #
47
51
  # @see #score
48
52
  # @see #classifications
49
53
  def classify(words)
50
- category = classifications(words).max_by { |score| score[1] }
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
@@ -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 [Symbol] category_name a category name
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}"
@@ -7,25 +7,25 @@ class Inferx
7
7
 
8
8
  # Get all category names.
9
9
  #
10
- # @return [Array<Symbol>] category names
10
+ # @return [Array<String>] category names
11
11
  def all
12
- (hkeys || []).map(&:to_sym)
12
+ hkeys || []
13
13
  end
14
14
 
15
15
  # Get a category according the name.
16
16
  #
17
- # @param [Symbol] category_name a category name
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, "'#{category_name}' is missing" unless size
22
- spawn(Category, category_name, size.to_i)
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<Symbol>] category_names category names
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<Symbol>] category_names category names
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 spawn(Category, category_name, size.to_i)
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
@@ -4,21 +4,21 @@ class Inferx
4
4
  class Category < Adapter
5
5
 
6
6
  # @param [Redis] redis an instance of Redis
7
- # @param [Symbol] name a category name
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 [Symbol] a category name
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
- increase = collect(words).inject(0) do |count, pair|
62
- zincrby(pair[1], pair[0])
63
- count + pair[1]
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
- decrease = 0
86
+ return if words.empty?
87
87
 
88
- values = @redis.pipelined do
89
- decrease = collect(words).inject(0) do |count, pair|
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
- values[0..-2].each do |score|
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
- if decrease > 0
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
- private
130
+ protected
127
131
 
128
132
  %w(zrevrange zscore zincrby zremrangebyscore).each do |command|
129
133
  define_method(command) do |*args|
@@ -0,0 +1,14 @@
1
+ require 'inferx/categories'
2
+ require 'inferx/complementary/category'
3
+
4
+ class Inferx
5
+ module Complementary
6
+ class Categories < Inferx::Categories
7
+ protected
8
+
9
+ def spawn_category(*args)
10
+ spawn(Category, *args)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -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
@@ -1,3 +1,3 @@
1
1
  class Inferx
2
- VERSION = '0.2.1'
2
+ VERSION = '0.2.2'
3
3
  end
@@ -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(:red).should == 'inferx:categories:red'
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(:red).should == 'inferx:example:categories:red'
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).and_return('klass') }
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).and_return([])
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', :red).and_return('2')
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(:red)
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).and_return('2')
52
+ s.stub!(:hget => '2')
62
53
  end
63
54
 
64
- Inferx::Category.should_receive(:new).with(redis, :red, 2, :namespace => 'example', :manual => true)
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(:red)
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).and_return('2')
62
+ s.stub!(:hget => '2')
72
63
  end
73
64
 
74
65
  categories = described_class.new(redis)
75
- categories.get(:red).should be_an(Inferx::Category)
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).and_return(nil)
72
+ s.stub!(:hget => nil)
82
73
  end
83
74
 
84
75
  categories = described_class.new(redis)
85
- lambda { categories.get(:red) }.should raise_error(ArgumentError, /'red' is missing/)
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', :red, 0)
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(:red)
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', :red, 0)
104
- s.should_receive(:hsetnx).with('inferx:categories', :green, 0)
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(:red, :green)
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(:red)
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', :red)
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(:red)
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', :red)
140
- s.should_receive(:hdel).with('inferx:categories', :green)
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(:red, :green)
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(:red)
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).and_return(%w(red green blue))
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, :red, 2, :namespace => 'example', :manual => true)
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, :red, 2)
15
- category.name.should == :red
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, :red, 2)
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, :red, 2)
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, :red, 2)
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, :red, 2)
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, :red, 2)
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, :red, 2)
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', :red, 5)
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, :red, 2)
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, :red, 2)
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, :red, 2)
107
- category.train(%w())
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, :red, 2, :manual => true)
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, :red, 2)
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).and_return(%w(3 -2 1))
144
- s.should_receive(:hincrby).with('inferx:categories', :red, -3)
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, :red, 7)
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).and_return(%w(3 -2 1))
165
+ s.stub!(:zremrangebyscore)
156
166
  s.stub!(:hincrby)
157
167
  end
158
168
 
159
- category = described_class.new(redis, :red, 7)
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).and_return(%w(-2 -3 2))
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, :red, 7)
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).and_return(%w(3 -2 1))
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, :red, 7, :manual => true)
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, :red, 2)
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, :red, 2)
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).and_return(%w(2 3))
239
+ s.stub!(:pipelined => %w(2 3))
220
240
  end
221
241
 
222
- category = described_class.new(redis, :red, 2)
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).and_return(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).and_return(0)
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).and_return(2)
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 = [:red, :green, :blue].map do |category_name|
84
- stub.tap { |s| s.stub!(:name).and_return(category_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
- :red => 'score of red',
114
- :green => 'score of green',
115
- :blue => 'score of blue'
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).and_return(:red => -2, :green => -1, :blue => -3)
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(:red => -2)
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 == :green
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).and_return({})
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
@@ -3,6 +3,6 @@ def redis_stub
3
3
  s.stub!(:pipelined).and_return { |&block| block.call }
4
4
  s.stub!(:save)
5
5
  yield s if block_given?
6
- Redis.stub!(:new).and_return(s) if defined? Redis
6
+ Redis.stub!(:new => s) if defined? Redis
7
7
  end
8
8
  end
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.1
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-17 00:00:00.000000000 Z
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: