inferx 0.1.7 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -46,7 +46,7 @@ class Inferx
46
46
  # Spawn an instance of any class.
47
47
  #
48
48
  # @param [Class] klass any class, constructor takes the instance of Redis to
49
- # first argument, and takes the namespace to last argument
49
+ # first argument, and takes the options to last argument
50
50
  # @param [Array] args any arguments
51
51
  # @return [Object] a instance of the class
52
52
  def spawn(klass, *args)
@@ -55,7 +55,7 @@ class Inferx
55
55
 
56
56
  protected
57
57
 
58
- %w(hdel hexists hget hincrby hkeys hsetnx).each do |command|
58
+ %w(hdel hget hgetall hincrby hkeys hsetnx).each do |command|
59
59
  define_method(command) do |*args|
60
60
  @redis.__send__(command, categories_key, *args)
61
61
  end
@@ -14,11 +14,12 @@ class Inferx
14
14
 
15
15
  # Get a category according the name.
16
16
  #
17
- # @param [Symbol] category_name category name
18
- # @return [Inferx::Category] category
17
+ # @param [Symbol] category_name a category name
18
+ # @return [Inferx::Category] a category
19
19
  def get(category_name)
20
- raise ArgumentError, "'#{category_name}' is missing" unless hexists(category_name)
21
- spawn(Category, category_name)
20
+ size = hget(category_name)
21
+ raise ArgumentError, "'#{category_name}' is missing" unless size
22
+ spawn(Category, category_name, size.to_i)
22
23
  end
23
24
  alias [] get
24
25
 
@@ -46,9 +47,11 @@ class Inferx
46
47
  # Apply process for each category.
47
48
  #
48
49
  # @yield a block to be called for every category
49
- # @yieldparam [Inferx::Category] category category
50
+ # @yieldparam [Inferx::Category] category a category
50
51
  def each
51
- all.each { |category_name| yield spawn(Category, category_name) }
52
+ hgetall.each do |category_name, size|
53
+ yield spawn(Category, category_name, size.to_i)
54
+ end
52
55
  end
53
56
  end
54
57
  end
@@ -5,36 +5,34 @@ class Inferx
5
5
 
6
6
  # @param [Redis] redis an instance of Redis
7
7
  # @param [Symbol] name a category name
8
+ # @param [Integer] size total of scores
8
9
  # @param [Hash] options
9
10
  # @option options [String] :namespace namespace of keys to be used to Redis
10
11
  # @option options [Boolean] :manual whether manual save, defaults to false
11
- def initialize(redis, name, options = {})
12
+ def initialize(redis, name, size, options = {})
12
13
  super(redis, options)
13
14
  @name = name
15
+ @size = size
14
16
  end
15
17
 
16
18
  # Get a category name.
17
19
  #
18
20
  # @attribute [r] name
19
21
  # @return [Symbol] a category name
20
- attr_reader :name
22
+
23
+ # Get total of scores.
24
+ #
25
+ # @attribute [r] size
26
+ # @return [Integer] total of scores
27
+ attr_reader :name, :size
21
28
 
22
29
  # Get words with scores in the category.
23
30
  #
24
- # @param [Hash] options
25
- # @option options [Integer] :score lower limit for getting by score
26
- # @option options [Integer] :rank upper limit for getting by rank
27
31
  # @return [Hash<String, Integer>] words with scores
28
- def all(options = {})
29
- words_with_scores = if score = options[:score]
30
- zrevrangebyscore('+inf', score, :withscores => true)
31
- else
32
- rank = options[:rank] || -1
33
- zrevrange(0, rank, :withscores => true)
34
- end
35
-
36
- size = words_with_scores.size
32
+ def all
33
+ words_with_scores = zrevrange(0, -1, :withscores => true)
37
34
  index = 1
35
+ size = words_with_scores.size
38
36
 
39
37
  while index < size
40
38
  words_with_scores[index] = words_with_scores[index].to_i
@@ -68,6 +66,7 @@ class Inferx
68
66
  if increase > 0
69
67
  hincrby(name, increase)
70
68
  @redis.save unless manual?
69
+ @size += increase
71
70
  end
72
71
  end
73
72
  end
@@ -95,43 +94,22 @@ class Inferx
95
94
  if decrease > 0
96
95
  hincrby(name, -decrease)
97
96
  @redis.save unless manual?
97
+ @size -= decrease
98
98
  end
99
99
  end
100
100
 
101
- # Get total of scores.
102
- #
103
- # @return [Integer] total of scores
104
- def size
105
- (hget(name) || 0).to_i
106
- end
107
-
108
101
  # Get effectively scores for each word.
109
102
  #
110
- # @param [Array<String>] words a set of words
111
- # @param [Hash<String, Integer>] words_with_scores words with scores
112
- # prepared in advance for reduce access to Redis
103
+ # @param [Array<String>] words an array of words
113
104
  # @return [Array<Integer>] scores for each word
114
- def scores(words, words_with_scores = {})
115
- scores = @redis.pipelined do
116
- words.each do |word|
117
- zscore(word) unless words_with_scores[word]
118
- end
119
- end
120
-
121
- index = 0
122
-
123
- next_score = lambda do
124
- score = scores[index]
125
- index += 1
126
- score ? score.to_i : nil
127
- end
128
-
129
- words.map { |word| words_with_scores[word] || next_score[] }
105
+ def scores(words)
106
+ scores = @redis.pipelined { words.map(&method(:zscore)) }
107
+ scores.map { |score| score ? score.to_i : nil }
130
108
  end
131
109
 
132
110
  private
133
111
 
134
- %w(zrevrange zrevrangebyscore zscore zincrby zremrangebyscore).each do |command|
112
+ %w(zrevrange zscore zincrby zremrangebyscore).each do |command|
135
113
  define_method(command) do |*args|
136
114
  @category_key ||= make_category_key(@name)
137
115
  @redis.__send__(command, @category_key, *args)
@@ -1,3 +1,3 @@
1
1
  class Inferx
2
- VERSION = '0.1.7'
2
+ VERSION = '0.2.0'
3
3
  end
data/lib/inferx.rb CHANGED
@@ -9,7 +9,7 @@ class Inferx
9
9
  # {https://github.com/redis/redis-rb redis}
10
10
  #
11
11
  # @option options [String] :namespace namespace of keys to be used to Redis
12
- # @option options [Boolean] :manual whether save manually, defaults to false
12
+ # @option options [Boolean] :manual whether manual save, defaults to false
13
13
  def initialize(options = {})
14
14
  @categories = Categories.new(Redis.new(options), options)
15
15
  end
@@ -24,9 +24,8 @@ class Inferx
24
24
  def score(category, words)
25
25
  size = category.size.to_f
26
26
  return -Float::INFINITY unless size > 0
27
- words_with_scores = category.all(:rank => 500)
28
- scores = category.scores(words, words_with_scores)
29
- scores.inject(0) { |s, score| s + Math.log((score || 0.1) / size) }
27
+ scores = category.scores(words)
28
+ scores.inject(0.0) { |s, score| s + Math.log((score || 0.1) / size) }
30
29
  end
31
30
 
32
31
  # Get a score for each category according to a set of words.
@@ -63,7 +63,7 @@ describe Inferx::Adapter, '#make_categories_key' do
63
63
  end
64
64
 
65
65
  describe Inferx::Adapter, '#make_category_key' do
66
- it 'returns the key for access to to scores stored each by word' do
66
+ it 'returns the key for access to scores stored each by word' do
67
67
  adapter = described_class.new(redis_stub)
68
68
  adapter.make_category_key(:red).should == 'inferx:categories:red'
69
69
  end
@@ -47,9 +47,9 @@ describe Inferx::Categories, '#all' do
47
47
  end
48
48
 
49
49
  describe Inferx::Categories, '#get' do
50
- it 'calls Redis#hexists' do
50
+ it 'calls Redis#hget' do
51
51
  redis = redis_stub do |s|
52
- s.should_receive(:hexists).with('inferx:categories', :red).and_return(true)
52
+ s.should_receive(:hget).with('inferx:categories', :red).and_return('2')
53
53
  end
54
54
 
55
55
  categories = described_class.new(redis)
@@ -58,17 +58,17 @@ describe Inferx::Categories, '#get' do
58
58
 
59
59
  it 'calles Inferx::Category.new with the instance of Redis, the category name and the options' do
60
60
  redis = redis_stub do |s|
61
- s.stub!(:hexists).and_return(true)
61
+ s.stub!(:hget).and_return('2')
62
62
  end
63
63
 
64
- Inferx::Category.should_receive(:new).with(redis, :red, :namespace => 'example', :manual => true)
64
+ Inferx::Category.should_receive(:new).with(redis, :red, 2, :namespace => 'example', :manual => true)
65
65
  categories = described_class.new(redis, :namespace => 'example', :manual => true)
66
66
  categories.get(:red)
67
67
  end
68
68
 
69
69
  it 'returns an instance of Inferx::Category' do
70
70
  redis = redis_stub do |s|
71
- s.stub!(:hexists).and_return(true)
71
+ s.stub!(:hget).and_return('2')
72
72
  end
73
73
 
74
74
  categories = described_class.new(redis)
@@ -78,7 +78,7 @@ describe Inferx::Categories, '#get' do
78
78
  context 'with a missing category' do
79
79
  it 'raises ArgumentError' do
80
80
  redis = redis_stub do |s|
81
- s.stub!(:hexists).and_return(false)
81
+ s.stub!(:hget).and_return(nil)
82
82
  end
83
83
 
84
84
  categories = described_class.new(redis)
@@ -163,7 +163,7 @@ end
163
163
  describe Inferx::Categories, '#each' do
164
164
  before do
165
165
  @redis = redis_stub do |s|
166
- s.stub!(:hkeys).and_return(%w(red green blue))
166
+ s.stub!(:hgetall).and_return(%w(red green blue))
167
167
  end
168
168
  end
169
169
 
@@ -4,25 +4,30 @@ 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, :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)
14
+ category = described_class.new(redis_stub, :red, 2)
15
15
  category.name.should == :red
16
16
  end
17
+
18
+ it 'sets the size to the size attribute' do
19
+ category = described_class.new(redis_stub, :red, 2)
20
+ category.size.should == 2
21
+ end
17
22
  end
18
23
 
19
24
  describe Inferx::Category, '#all' do
20
- it 'calls Redis#revrange' do
25
+ it 'calls Redis#zrevrange' do
21
26
  redis = redis_stub do |s|
22
27
  s.should_receive(:zrevrange).with('inferx:categories:red', 0, -1, :withscores => true).and_return([])
23
28
  end
24
29
 
25
- category = described_class.new(redis, :red)
30
+ category = described_class.new(redis, :red, 2)
26
31
  category.all
27
32
  end
28
33
 
@@ -31,31 +36,9 @@ describe Inferx::Category, '#all' do
31
36
  s.stub!(:zrevrange).with('inferx:categories:red', 0, -1, :withscores => true).and_return(%w(apple 2 strawberry 3))
32
37
  end
33
38
 
34
- category = described_class.new(redis, :red)
39
+ category = described_class.new(redis, :red, 2)
35
40
  category.all.should == {'apple' => 2, 'strawberry' => 3}
36
41
  end
37
-
38
- context 'with the rank option' do
39
- it 'calls Redis#zrevrange' do
40
- redis = redis_stub do |s|
41
- s.should_receive(:zrevrange).with('inferx:categories:red', 0, 1000, :withscores => true).and_return([])
42
- end
43
-
44
- category = described_class.new(redis, :red)
45
- category.all(:rank => 1000)
46
- end
47
- end
48
-
49
- context 'with the score option' do
50
- it 'calls Redis#zrevrangebyscore' do
51
- redis = redis_stub do |s|
52
- s.should_receive(:zrevrangebyscore).with('inferx:categories:red', '+inf', 2, :withscores => true).and_return([])
53
- end
54
-
55
- category = described_class.new(redis, :red)
56
- category.all(:score => 2)
57
- end
58
- end
59
42
  end
60
43
 
61
44
  describe Inferx::Category, '#get' do
@@ -64,7 +47,7 @@ describe Inferx::Category, '#get' do
64
47
  s.should_receive(:zscore).with('inferx:categories:red', 'apple')
65
48
  end
66
49
 
67
- category = described_class.new(redis, :red)
50
+ category = described_class.new(redis, :red, 2)
68
51
  category.get('apple')
69
52
  end
70
53
 
@@ -73,7 +56,7 @@ describe Inferx::Category, '#get' do
73
56
  s.stub!(:zscore).with('inferx:categories:red', 'apple').and_return('1')
74
57
  end
75
58
 
76
- category = described_class.new(redis, :red)
59
+ category = described_class.new(redis, :red, 2)
77
60
  category.get('apple').should == 1
78
61
  end
79
62
 
@@ -83,7 +66,7 @@ describe Inferx::Category, '#get' do
83
66
  s.stub!(:zscore).with('inferx:categories:red', 'strawberry').and_return(nil)
84
67
  end
85
68
 
86
- category = described_class.new(redis, :red)
69
+ category = described_class.new(redis, :red, 2)
87
70
  category.get('strawberry').should be_nil
88
71
  end
89
72
  end
@@ -98,8 +81,19 @@ describe Inferx::Category, '#train' do
98
81
  s.should_receive(:save)
99
82
  end
100
83
 
101
- category = described_class.new(redis, :red)
84
+ category = described_class.new(redis, :red, 2)
85
+ category.train(%w(apple strawberry apple strawberry strawberry))
86
+ end
87
+
88
+ it 'increases the size attribute' do
89
+ redis = redis_stub do |s|
90
+ s.stub!(:zincrby)
91
+ s.stub!(:hincrby)
92
+ end
93
+
94
+ category = described_class.new(redis, :red, 2)
102
95
  category.train(%w(apple strawberry apple strawberry strawberry))
96
+ category.size.should == 7
103
97
  end
104
98
 
105
99
  context 'with no update' do
@@ -109,7 +103,7 @@ describe Inferx::Category, '#train' do
109
103
  s.should_not_receive(:save)
110
104
  end
111
105
 
112
- category = described_class.new(redis, :red)
106
+ category = described_class.new(redis, :red, 2)
113
107
  category.train(%w())
114
108
  end
115
109
  end
@@ -122,7 +116,7 @@ describe Inferx::Category, '#train' do
122
116
  s.should_not_receive(:save)
123
117
  end
124
118
 
125
- category = described_class.new(redis, :red, :manual => true)
119
+ category = described_class.new(redis, :red, 2, :manual => true)
126
120
  category.train(%w(apple strawberry apple strawberry strawberry))
127
121
  end
128
122
  end
@@ -138,10 +132,22 @@ describe Inferx::Category, '#untrain' do
138
132
  s.should_receive(:save)
139
133
  end
140
134
 
141
- category = described_class.new(redis, :red)
135
+ category = described_class.new(redis, :red, 7)
142
136
  category.untrain(%w(apple strawberry apple strawberry strawberry))
143
137
  end
144
138
 
139
+ it 'decreases the size attribute' do
140
+ redis = redis_stub do |s|
141
+ s.stub!(:zincrby)
142
+ s.stub!(:zremrangebyscore).and_return(%w(3 -2 1))
143
+ s.stub!(:hincrby)
144
+ end
145
+
146
+ category = described_class.new(redis, :red, 7)
147
+ category.untrain(%w(apple strawberry apple strawberry strawberry))
148
+ category.size.should == 4
149
+ end
150
+
145
151
  context 'with no update' do
146
152
  it 'does not call Redis#hincrby' do
147
153
  redis = redis_stub do |s|
@@ -151,7 +157,7 @@ describe Inferx::Category, '#untrain' do
151
157
  s.should_not_receive(:save)
152
158
  end
153
159
 
154
- category = described_class.new(redis, :red)
160
+ category = described_class.new(redis, :red, 7)
155
161
  category.untrain(%w(apple strawberry apple strawberry strawberry))
156
162
  end
157
163
  end
@@ -165,43 +171,12 @@ describe Inferx::Category, '#untrain' do
165
171
  s.should_not_receive(:save)
166
172
  end
167
173
 
168
- category = described_class.new(redis, :red, :manual => true)
174
+ category = described_class.new(redis, :red, 7, :manual => true)
169
175
  category.untrain(%w(apple strawberry apple strawberry strawberry))
170
176
  end
171
177
  end
172
178
  end
173
179
 
174
- describe Inferx::Category, '#size' do
175
- it 'calls Redis#hget' do
176
- redis = redis_stub do |s|
177
- s.should_receive(:hget).with('inferx:categories', :red)
178
- end
179
-
180
- category = described_class.new(redis, :red)
181
- category.size
182
- end
183
-
184
- it 'returns total of the score of the words as Integer' do
185
- redis = redis_stub do |s|
186
- s.stub!(:hget).and_return('1')
187
- end
188
-
189
- category = described_class.new(redis, :red)
190
- category.size.should == 1
191
- end
192
-
193
- context 'with the missing key' do
194
- it 'returns 0' do
195
- redis = redis_stub do |s|
196
- s.stub!(:hget).and_return(nil)
197
- end
198
-
199
- category = described_class.new(redis, :red)
200
- category.size.should == 0
201
- end
202
- end
203
- end
204
-
205
180
  describe Inferx::Category, '#scores' do
206
181
  it 'calls Redis#zscore' do
207
182
  redis = redis_stub do |s|
@@ -209,7 +184,7 @@ describe Inferx::Category, '#scores' do
209
184
  s.should_receive(:zscore).with('inferx:categories:red', 'strawberry')
210
185
  end
211
186
 
212
- category = described_class.new(redis, :red)
187
+ category = described_class.new(redis, :red, 2)
213
188
  category.scores(%w(apple strawberry))
214
189
  end
215
190
 
@@ -218,21 +193,8 @@ describe Inferx::Category, '#scores' do
218
193
  s.stub!(:pipelined).and_return(%w(2 3))
219
194
  end
220
195
 
221
- category = described_class.new(redis, :red)
196
+ category = described_class.new(redis, :red, 2)
222
197
  scores = category.scores(%w(apple strawberry))
223
198
  scores.should == [2, 3]
224
199
  end
225
-
226
- context 'with words with scores' do
227
- it 'returns the scores to use the cache' do
228
- redis = redis_stub do |s|
229
- s.should_not_receive(:zscore).with('inferx:categories:red', 'strawberry')
230
- s.stub!(:pipelined).and_return { |&block| block.call; [2] }
231
- end
232
-
233
- category = described_class.new(redis, :red)
234
- scores = category.scores(%w(apple strawberry), 'strawberry' => 3, 'hoge' => 1)
235
- scores.should == [2, 3]
236
- end
237
- end
238
200
  end
data/spec/inferx_spec.rb CHANGED
@@ -12,7 +12,7 @@ describe Inferx do
12
12
  end
13
13
 
14
14
  describe Inferx, '#initialize' do
15
- it "calls #{described_class}::Categories.new with a connection of Redis and the options" do
15
+ it "calls #{described_class}::Categories.new with an instance of Redis and the options" do
16
16
  redis = redis_stub
17
17
  Inferx::Categories.should_receive(:new).with(redis, :namespace => 'example', :manual => true)
18
18
  described_class.new(:namespace => 'example', :manual => true)
@@ -30,11 +30,10 @@ describe Inferx, '#score' do
30
30
  @inferx = described_class.new
31
31
  end
32
32
 
33
- it "calls #{described_class}::Categories#size, #{described_class}::Category#all and #{described_class}::Category#scores" do
33
+ it "calls #{described_class}::Category#size and #{described_class}::Category#scores" do
34
34
  category = mock.tap do |m|
35
35
  m.should_receive(:size).and_return(5)
36
- m.should_receive(:all).with(:rank => 500).and_return('apple' => 2)
37
- m.should_receive(:scores).with(%w(apple), 'apple' => 2).and_return([2])
36
+ m.should_receive(:scores).with(%w(apple)).and_return([2])
38
37
  end
39
38
 
40
39
  @inferx.score(category, %w(apple))
@@ -51,7 +50,6 @@ describe Inferx, '#score' do
51
50
  category = stub.tap do |s|
52
51
  s.stub!(:size).and_return(size)
53
52
  s.stub!(:scores).and_return(scores)
54
- s.stub!(:all)
55
53
  end
56
54
 
57
55
  @inferx.score(category, words).should == expected
@@ -67,6 +65,17 @@ describe Inferx, '#score' do
67
65
  score.should be_infinite
68
66
  score.should < 0
69
67
  end
68
+
69
+ it 'returns 0.0 if the words are empty' do
70
+ category = stub.tap do |s|
71
+ s.stub!(:size).and_return(2)
72
+ s.stub!(:scores).and_return([])
73
+ end
74
+
75
+ score = @inferx.score(category, [])
76
+ score.should be_a(Float)
77
+ score.should be_zero
78
+ end
70
79
  end
71
80
 
72
81
  describe Inferx, '#classifications' do
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.1.7
4
+ version: 0.2.0
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-04-16 00:00:00.000000000 Z
12
+ date: 2012-04-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: redis