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 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: