Skip to content
Browse files

Merge branch 'release-0.2.0'

  • Loading branch information...
2 parents c7cad43 + ee8d6cb commit 1f14be289fbe2c6cc33622d9174e8c8baa7e066b Takahiro Kondo committed Apr 18, 2012
View
7 lib/inferx.rb
@@ -9,7 +9,7 @@ class Inferx
# {https://github.com/redis/redis-rb redis}
#
# @option options [String] :namespace namespace of keys to be used to Redis
- # @option options [Boolean] :manual whether save manually, defaults to false
+ # @option options [Boolean] :manual whether manual save, defaults to false
def initialize(options = {})
@categories = Categories.new(Redis.new(options), options)
end
@@ -24,9 +24,8 @@ def initialize(options = {})
def score(category, words)
size = category.size.to_f
return -Float::INFINITY unless size > 0
- words_with_scores = category.all(:rank => 500)
- scores = category.scores(words, words_with_scores)
- scores.inject(0) { |s, score| s + Math.log((score || 0.1) / size) }
+ scores = category.scores(words)
+ scores.inject(0.0) { |s, score| s + Math.log((score || 0.1) / size) }
end
# Get a score for each category according to a set of words.
View
4 lib/inferx/adapter.rb
@@ -46,7 +46,7 @@ def make_category_key(category_name)
# Spawn an instance of any class.
#
# @param [Class] klass any class, constructor takes the instance of Redis to
- # first argument, and takes the namespace to last argument
+ # first argument, and takes the options to last argument
# @param [Array] args any arguments
# @return [Object] a instance of the class
def spawn(klass, *args)
@@ -55,7 +55,7 @@ def spawn(klass, *args)
protected
- %w(hdel hexists hget hincrby hkeys hsetnx).each do |command|
+ %w(hdel hget hgetall hincrby hkeys hsetnx).each do |command|
define_method(command) do |*args|
@redis.__send__(command, categories_key, *args)
end
View
15 lib/inferx/categories.rb
@@ -14,11 +14,12 @@ def all
# Get a category according the name.
#
- # @param [Symbol] category_name category name
- # @return [Inferx::Category] category
+ # @param [Symbol] category_name a category name
+ # @return [Inferx::Category] a category
def get(category_name)
- raise ArgumentError, "'#{category_name}' is missing" unless hexists(category_name)
- spawn(Category, category_name)
+ size = hget(category_name)
+ raise ArgumentError, "'#{category_name}' is missing" unless size
+ spawn(Category, category_name, size.to_i)
end
alias [] get
@@ -46,9 +47,11 @@ def remove(*category_names)
# Apply process for each category.
#
# @yield a block to be called for every category
- # @yieldparam [Inferx::Category] category category
+ # @yieldparam [Inferx::Category] category a category
def each
- all.each { |category_name| yield spawn(Category, category_name) }
+ hgetall.each do |category_name, size|
+ yield spawn(Category, category_name, size.to_i)
+ end
end
end
end
View
60 lib/inferx/category.rb
@@ -5,36 +5,34 @@ class Category < Adapter
# @param [Redis] redis an instance of Redis
# @param [Symbol] name a category name
+ # @param [Integer] size total of scores
# @param [Hash] options
# @option options [String] :namespace namespace of keys to be used to Redis
# @option options [Boolean] :manual whether manual save, defaults to false
- def initialize(redis, name, options = {})
+ def initialize(redis, name, size, options = {})
super(redis, options)
@name = name
+ @size = size
end
# Get a category name.
#
# @attribute [r] name
# @return [Symbol] a category name
- attr_reader :name
+
+ # Get total of scores.
+ #
+ # @attribute [r] size
+ # @return [Integer] total of scores
+ attr_reader :name, :size
# Get words with scores in the category.
#
- # @param [Hash] options
- # @option options [Integer] :score lower limit for getting by score
- # @option options [Integer] :rank upper limit for getting by rank
# @return [Hash<String, Integer>] words with scores
- def all(options = {})
- words_with_scores = if score = options[:score]
- zrevrangebyscore('+inf', score, :withscores => true)
- else
- rank = options[:rank] || -1
- zrevrange(0, rank, :withscores => true)
- end
-
- size = words_with_scores.size
+ def all
+ words_with_scores = zrevrange(0, -1, :withscores => true)
index = 1
+ size = words_with_scores.size
while index < size
words_with_scores[index] = words_with_scores[index].to_i
@@ -68,6 +66,7 @@ def train(words)
if increase > 0
hincrby(name, increase)
@redis.save unless manual?
+ @size += increase
end
end
end
@@ -95,43 +94,22 @@ def untrain(words)
if decrease > 0
hincrby(name, -decrease)
@redis.save unless manual?
+ @size -= decrease
end
end
- # Get total of scores.
- #
- # @return [Integer] total of scores
- def size
- (hget(name) || 0).to_i
- end
-
# Get effectively scores for each word.
#
- # @param [Array<String>] words a set of words
- # @param [Hash<String, Integer>] words_with_scores words with scores
- # prepared in advance for reduce access to Redis
+ # @param [Array<String>] words an array of words
# @return [Array<Integer>] scores for each word
- def scores(words, words_with_scores = {})
- scores = @redis.pipelined do
- words.each do |word|
- zscore(word) unless words_with_scores[word]
- end
- end
-
- index = 0
-
- next_score = lambda do
- score = scores[index]
- index += 1
- score ? score.to_i : nil
- end
-
- words.map { |word| words_with_scores[word] || next_score[] }
+ def scores(words)
+ scores = @redis.pipelined { words.map(&method(:zscore)) }
+ scores.map { |score| score ? score.to_i : nil }
end
private
- %w(zrevrange zrevrangebyscore zscore zincrby zremrangebyscore).each do |command|
+ %w(zrevrange zscore zincrby zremrangebyscore).each do |command|
define_method(command) do |*args|
@category_key ||= make_category_key(@name)
@redis.__send__(command, @category_key, *args)
View
2 lib/inferx/version.rb
@@ -1,3 +1,3 @@
class Inferx
- VERSION = '0.1.7'
+ VERSION = '0.2.0'
end
View
2 spec/inferx/adapter_spec.rb
@@ -63,7 +63,7 @@
end
describe Inferx::Adapter, '#make_category_key' do
- it 'returns the key for access to to scores stored each by word' do
+ it 'returns the key for access to scores stored each by word' do
adapter = described_class.new(redis_stub)
adapter.make_category_key(:red).should == 'inferx:categories:red'
end
View
14 spec/inferx/categories_spec.rb
@@ -47,9 +47,9 @@
end
describe Inferx::Categories, '#get' do
- it 'calls Redis#hexists' do
+ it 'calls Redis#hget' do
redis = redis_stub do |s|
- s.should_receive(:hexists).with('inferx:categories', :red).and_return(true)
+ s.should_receive(:hget).with('inferx:categories', :red).and_return('2')
end
categories = described_class.new(redis)
@@ -58,17 +58,17 @@
it 'calles Inferx::Category.new with the instance of Redis, the category name and the options' do
redis = redis_stub do |s|
- s.stub!(:hexists).and_return(true)
+ s.stub!(:hget).and_return('2')
end
- Inferx::Category.should_receive(:new).with(redis, :red, :namespace => 'example', :manual => true)
+ Inferx::Category.should_receive(:new).with(redis, :red, 2, :namespace => 'example', :manual => true)
categories = described_class.new(redis, :namespace => 'example', :manual => true)
categories.get(:red)
end
it 'returns an instance of Inferx::Category' do
redis = redis_stub do |s|
- s.stub!(:hexists).and_return(true)
+ s.stub!(:hget).and_return('2')
end
categories = described_class.new(redis)
@@ -78,7 +78,7 @@
context 'with a missing category' do
it 'raises ArgumentError' do
redis = redis_stub do |s|
- s.stub!(:hexists).and_return(false)
+ s.stub!(:hget).and_return(nil)
end
categories = described_class.new(redis)
@@ -163,7 +163,7 @@
describe Inferx::Categories, '#each' do
before do
@redis = redis_stub do |s|
- s.stub!(:hkeys).and_return(%w(red green blue))
+ s.stub!(:hgetall).and_return(%w(red green blue))
end
end
View
126 spec/inferx/category_spec.rb
@@ -4,25 +4,30 @@
describe Inferx::Category, '#initialize' do
it 'calls Inferx::Adapter#initialize' do
redis = redis_stub
- category = described_class.new(redis, :red, :namespace => 'example', :manual => true)
+ category = described_class.new(redis, :red, 2, :namespace => 'example', :manual => true)
category.instance_eval { @redis }.should == redis
category.instance_eval { @namespace }.should == 'example'
category.should be_manual
end
it 'sets the category name to the name attribute' do
- category = described_class.new(redis_stub, :red)
+ category = described_class.new(redis_stub, :red, 2)
category.name.should == :red
end
+
+ it 'sets the size to the size attribute' do
+ category = described_class.new(redis_stub, :red, 2)
+ category.size.should == 2
+ end
end
describe Inferx::Category, '#all' do
- it 'calls Redis#revrange' do
+ it 'calls Redis#zrevrange' do
redis = redis_stub do |s|
s.should_receive(:zrevrange).with('inferx:categories:red', 0, -1, :withscores => true).and_return([])
end
- category = described_class.new(redis, :red)
+ category = described_class.new(redis, :red, 2)
category.all
end
@@ -31,31 +36,9 @@
s.stub!(:zrevrange).with('inferx:categories:red', 0, -1, :withscores => true).and_return(%w(apple 2 strawberry 3))
end
- category = described_class.new(redis, :red)
+ category = described_class.new(redis, :red, 2)
category.all.should == {'apple' => 2, 'strawberry' => 3}
end
-
- context 'with the rank option' do
- it 'calls Redis#zrevrange' do
- redis = redis_stub do |s|
- s.should_receive(:zrevrange).with('inferx:categories:red', 0, 1000, :withscores => true).and_return([])
- end
-
- category = described_class.new(redis, :red)
- category.all(:rank => 1000)
- end
- end
-
- context 'with the score option' do
- it 'calls Redis#zrevrangebyscore' do
- redis = redis_stub do |s|
- s.should_receive(:zrevrangebyscore).with('inferx:categories:red', '+inf', 2, :withscores => true).and_return([])
- end
-
- category = described_class.new(redis, :red)
- category.all(:score => 2)
- end
- end
end
describe Inferx::Category, '#get' do
@@ -64,7 +47,7 @@
s.should_receive(:zscore).with('inferx:categories:red', 'apple')
end
- category = described_class.new(redis, :red)
+ category = described_class.new(redis, :red, 2)
category.get('apple')
end
@@ -73,7 +56,7 @@
s.stub!(:zscore).with('inferx:categories:red', 'apple').and_return('1')
end
- category = described_class.new(redis, :red)
+ category = described_class.new(redis, :red, 2)
category.get('apple').should == 1
end
@@ -83,7 +66,7 @@
s.stub!(:zscore).with('inferx:categories:red', 'strawberry').and_return(nil)
end
- category = described_class.new(redis, :red)
+ category = described_class.new(redis, :red, 2)
category.get('strawberry').should be_nil
end
end
@@ -98,8 +81,19 @@
s.should_receive(:save)
end
- category = described_class.new(redis, :red)
+ category = described_class.new(redis, :red, 2)
+ category.train(%w(apple strawberry apple strawberry strawberry))
+ end
+
+ it 'increases the size attribute' do
+ redis = redis_stub do |s|
+ s.stub!(:zincrby)
+ s.stub!(:hincrby)
+ end
+
+ category = described_class.new(redis, :red, 2)
category.train(%w(apple strawberry apple strawberry strawberry))
+ category.size.should == 7
end
context 'with no update' do
@@ -109,7 +103,7 @@
s.should_not_receive(:save)
end
- category = described_class.new(redis, :red)
+ category = described_class.new(redis, :red, 2)
category.train(%w())
end
end
@@ -122,7 +116,7 @@
s.should_not_receive(:save)
end
- category = described_class.new(redis, :red, :manual => true)
+ category = described_class.new(redis, :red, 2, :manual => true)
category.train(%w(apple strawberry apple strawberry strawberry))
end
end
@@ -138,10 +132,22 @@
s.should_receive(:save)
end
- category = described_class.new(redis, :red)
+ category = described_class.new(redis, :red, 7)
category.untrain(%w(apple strawberry apple strawberry strawberry))
end
+ it 'decreases the size attribute' do
+ redis = redis_stub do |s|
+ s.stub!(:zincrby)
+ s.stub!(:zremrangebyscore).and_return(%w(3 -2 1))
+ s.stub!(:hincrby)
+ end
+
+ category = described_class.new(redis, :red, 7)
+ category.untrain(%w(apple strawberry apple strawberry strawberry))
+ category.size.should == 4
+ end
+
context 'with no update' do
it 'does not call Redis#hincrby' do
redis = redis_stub do |s|
@@ -151,7 +157,7 @@
s.should_not_receive(:save)
end
- category = described_class.new(redis, :red)
+ category = described_class.new(redis, :red, 7)
category.untrain(%w(apple strawberry apple strawberry strawberry))
end
end
@@ -165,51 +171,20 @@
s.should_not_receive(:save)
end
- category = described_class.new(redis, :red, :manual => true)
+ category = described_class.new(redis, :red, 7, :manual => true)
category.untrain(%w(apple strawberry apple strawberry strawberry))
end
end
end
-describe Inferx::Category, '#size' do
- it 'calls Redis#hget' do
- redis = redis_stub do |s|
- s.should_receive(:hget).with('inferx:categories', :red)
- end
-
- category = described_class.new(redis, :red)
- category.size
- end
-
- it 'returns total of the score of the words as Integer' do
- redis = redis_stub do |s|
- s.stub!(:hget).and_return('1')
- end
-
- category = described_class.new(redis, :red)
- category.size.should == 1
- end
-
- context 'with the missing key' do
- it 'returns 0' do
- redis = redis_stub do |s|
- s.stub!(:hget).and_return(nil)
- end
-
- category = described_class.new(redis, :red)
- category.size.should == 0
- end
- end
-end
-
describe Inferx::Category, '#scores' do
it 'calls Redis#zscore' do
redis = redis_stub do |s|
s.should_receive(:zscore).with('inferx:categories:red', 'apple')
s.should_receive(:zscore).with('inferx:categories:red', 'strawberry')
end
- category = described_class.new(redis, :red)
+ category = described_class.new(redis, :red, 2)
category.scores(%w(apple strawberry))
end
@@ -218,21 +193,8 @@
s.stub!(:pipelined).and_return(%w(2 3))
end
- category = described_class.new(redis, :red)
+ category = described_class.new(redis, :red, 2)
scores = category.scores(%w(apple strawberry))
scores.should == [2, 3]
end
-
- context 'with words with scores' do
- it 'returns the scores to use the cache' do
- redis = redis_stub do |s|
- s.should_not_receive(:zscore).with('inferx:categories:red', 'strawberry')
- s.stub!(:pipelined).and_return { |&block| block.call; [2] }
- end
-
- category = described_class.new(redis, :red)
- scores = category.scores(%w(apple strawberry), 'strawberry' => 3, 'hoge' => 1)
- scores.should == [2, 3]
- end
- end
end
View
19 spec/inferx_spec.rb
@@ -12,7 +12,7 @@
end
describe Inferx, '#initialize' do
- it "calls #{described_class}::Categories.new with a connection of Redis and the options" do
+ it "calls #{described_class}::Categories.new with an instance of Redis and the options" do
redis = redis_stub
Inferx::Categories.should_receive(:new).with(redis, :namespace => 'example', :manual => true)
described_class.new(:namespace => 'example', :manual => true)
@@ -30,11 +30,10 @@
@inferx = described_class.new
end
- it "calls #{described_class}::Categories#size, #{described_class}::Category#all and #{described_class}::Category#scores" do
+ it "calls #{described_class}::Category#size and #{described_class}::Category#scores" do
category = mock.tap do |m|
m.should_receive(:size).and_return(5)
- m.should_receive(:all).with(:rank => 500).and_return('apple' => 2)
- m.should_receive(:scores).with(%w(apple), 'apple' => 2).and_return([2])
+ m.should_receive(:scores).with(%w(apple)).and_return([2])
end
@inferx.score(category, %w(apple))
@@ -51,7 +50,6 @@
category = stub.tap do |s|
s.stub!(:size).and_return(size)
s.stub!(:scores).and_return(scores)
- s.stub!(:all)
end
@inferx.score(category, words).should == expected
@@ -67,6 +65,17 @@
score.should be_infinite
score.should < 0
end
+
+ it 'returns 0.0 if the words are empty' do
+ category = stub.tap do |s|
+ s.stub!(:size).and_return(2)
+ s.stub!(:scores).and_return([])
+ end
+
+ score = @inferx.score(category, [])
+ score.should be_a(Float)
+ score.should be_zero
+ end
end
describe Inferx, '#classifications' do

0 comments on commit 1f14be2

Please sign in to comment.
Something went wrong with that request. Please try again.