inferx 0.1.5
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/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +50 -0
- data/Rakefile +2 -0
- data/inferx.gemspec +21 -0
- data/lib/inferx/adapter.rb +52 -0
- data/lib/inferx/categories.rb +53 -0
- data/lib/inferx/category.rb +139 -0
- data/lib/inferx/version.rb +3 -0
- data/lib/inferx.rb +56 -0
- data/spec/inferx/adapter_spec.rb +75 -0
- data/spec/inferx/categories_spec.rb +151 -0
- data/spec/inferx/category_spec.rb +206 -0
- data/spec/inferx_spec.rb +137 -0
- data/spec/spec_helper.rb +7 -0
- metadata +99 -0
    
        data/.gitignore
    ADDED
    
    
    
        data/Gemfile
    ADDED
    
    
    
        data/LICENSE
    ADDED
    
    | @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            Copyright (c) 2012 Takahiro Kondo
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            MIT License
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Permission is hereby granted, free of charge, to any person obtaining
         | 
| 6 | 
            +
            a copy of this software and associated documentation files (the
         | 
| 7 | 
            +
            "Software"), to deal in the Software without restriction, including
         | 
| 8 | 
            +
            without limitation the rights to use, copy, modify, merge, publish,
         | 
| 9 | 
            +
            distribute, sublicense, and/or sell copies of the Software, and to
         | 
| 10 | 
            +
            permit persons to whom the Software is furnished to do so, subject to
         | 
| 11 | 
            +
            the following conditions:
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            The above copyright notice and this permission notice shall be
         | 
| 14 | 
            +
            included in all copies or substantial portions of the Software.
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
         | 
| 17 | 
            +
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
         | 
| 18 | 
            +
            MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
         | 
| 19 | 
            +
            NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
         | 
| 20 | 
            +
            LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
         | 
| 21 | 
            +
            OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
         | 
| 22 | 
            +
            WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
         | 
    
        data/README.md
    ADDED
    
    | @@ -0,0 +1,50 @@ | |
| 1 | 
            +
            What is inferx
         | 
| 2 | 
            +
            ==============
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            It is Naive Bayes classifier, and the training data is kept always by Redis.
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            Installation
         | 
| 7 | 
            +
            ------------
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            Add this line to your application's Gemfile:
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                gem 'inferx'
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            And then execute:
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                $ bundle
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            Or install it yourself as:
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                $ gem install inferx
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            Usage
         | 
| 22 | 
            +
            -----
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                require 'inferx'
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                inferx = Inferx.new
         | 
| 27 | 
            +
                inferx.categories.add(:red, :green, :blue)
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                inferx.categories[:red].train(%w(
         | 
| 30 | 
            +
                  he
         | 
| 31 | 
            +
                  buy
         | 
| 32 | 
            +
                  apple
         | 
| 33 | 
            +
                  its
         | 
| 34 | 
            +
                  apple
         | 
| 35 | 
            +
                  fresh
         | 
| 36 | 
            +
                ))
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                inferx.categories[:green].train(%w(grasses ...))
         | 
| 39 | 
            +
                inferx.categories[:blue].train(%w(sea ...))
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                puts inferx.classify(%w(apple ...)) # => red
         | 
| 42 | 
            +
             | 
| 43 | 
            +
            Contributing
         | 
| 44 | 
            +
            ------------
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            1. Fork it
         | 
| 47 | 
            +
            2. Create your feature branch (`git checkout -b my-new-feature`)
         | 
| 48 | 
            +
            3. Commit your changes (`git commit -am 'Added some feature'`)
         | 
| 49 | 
            +
            4. Push to the branch (`git push origin my-new-feature`)
         | 
| 50 | 
            +
            5. Create new Pull Request
         | 
    
        data/Rakefile
    ADDED
    
    
    
        data/inferx.gemspec
    ADDED
    
    | @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            # -*- encoding: utf-8 -*-
         | 
| 2 | 
            +
            require File.expand_path('../lib/inferx/version', __FILE__)
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            Gem::Specification.new do |gem|
         | 
| 5 | 
            +
              gem.authors       = ["Takahiro Kondo"]
         | 
| 6 | 
            +
              gem.email         = ["kondo@atedesign.net"]
         | 
| 7 | 
            +
              gem.description   = %q{It is Naive Bayes classifier, and the training data is kept always by Redis.}
         | 
| 8 | 
            +
              gem.summary       = %q{Naive Bayes classifier, the training data on Redis}
         | 
| 9 | 
            +
              gem.homepage      = ""
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              gem.files         = `git ls-files`.split($\)
         | 
| 12 | 
            +
              gem.executables   = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
         | 
| 13 | 
            +
              gem.test_files    = gem.files.grep(%r{^(test|spec|features)/})
         | 
| 14 | 
            +
              gem.name          = "inferx"
         | 
| 15 | 
            +
              gem.require_paths = ["lib"]
         | 
| 16 | 
            +
              gem.version       = Inferx::VERSION
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              gem.add_runtime_dependency 'redis'
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              gem.add_development_dependency 'rspec'
         | 
| 21 | 
            +
            end
         | 
| @@ -0,0 +1,52 @@ | |
| 1 | 
            +
            class Inferx
         | 
| 2 | 
            +
              class Adapter
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                # @param [Redis] an instance of Redis
         | 
| 5 | 
            +
                # @param [String] namespace of keys to be used to Redis
         | 
| 6 | 
            +
                def initialize(redis, namespace = nil)
         | 
| 7 | 
            +
                  @redis = redis
         | 
| 8 | 
            +
                  @namespace = namespace
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                # Get the key for access to categories.
         | 
| 12 | 
            +
                #
         | 
| 13 | 
            +
                # @return [String] the key
         | 
| 14 | 
            +
                def categories_key
         | 
| 15 | 
            +
                  @categories_key ||= make_categories_key
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                # Make the key for access to categories.
         | 
| 19 | 
            +
                #
         | 
| 20 | 
            +
                # @return [String] the key
         | 
| 21 | 
            +
                def make_categories_key
         | 
| 22 | 
            +
                  parts = %w(inferx categories)
         | 
| 23 | 
            +
                  parts.insert(1, @namespace) if @namespace
         | 
| 24 | 
            +
                  parts.join(':')
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                # Make the key for access to scores stored each by word.
         | 
| 28 | 
            +
                #
         | 
| 29 | 
            +
                # @param [Symbol] a category name
         | 
| 30 | 
            +
                # @return [String] the key
         | 
| 31 | 
            +
                def make_category_key(category_name)
         | 
| 32 | 
            +
                  "#{categories_key}:#{category_name}"
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                # Spawn an instance of any class.
         | 
| 36 | 
            +
                #
         | 
| 37 | 
            +
                # @param [Class] any class, constructor takes the instance of Redis to
         | 
| 38 | 
            +
                #   first argument, and takes the namespace to last argument
         | 
| 39 | 
            +
                # @return [Object] a instance of the class
         | 
| 40 | 
            +
                def spawn(klass, *args)
         | 
| 41 | 
            +
                  klass.new(@redis, *args, @namespace)
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                protected
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                %w(hdel hexists hget hincrby hkeys hsetnx).each do |command|
         | 
| 47 | 
            +
                  define_method(command) do |*args|
         | 
| 48 | 
            +
                    @redis.__send__(command, categories_key, *args)
         | 
| 49 | 
            +
                  end
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
              end
         | 
| 52 | 
            +
            end
         | 
| @@ -0,0 +1,53 @@ | |
| 1 | 
            +
            require 'inferx/adapter'
         | 
| 2 | 
            +
            require 'inferx/category'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            class Inferx
         | 
| 5 | 
            +
              class Categories < Adapter
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                # Get all category names.
         | 
| 8 | 
            +
                #
         | 
| 9 | 
            +
                # @return [Array<Symbol>] category names
         | 
| 10 | 
            +
                def all
         | 
| 11 | 
            +
                  (hkeys || []).map(&:to_sym)
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                # Get a category according name.
         | 
| 15 | 
            +
                #
         | 
| 16 | 
            +
                # @param [Symbol] category name
         | 
| 17 | 
            +
                # @return [Inferx::Category] category
         | 
| 18 | 
            +
                def get(category_name)
         | 
| 19 | 
            +
                  raise ArgumentError, "'#{category_name}' is missing" unless hexists(category_name)
         | 
| 20 | 
            +
                  spawn(Category, category_name)
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
                alias [] get
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                # Add categories.
         | 
| 25 | 
            +
                #
         | 
| 26 | 
            +
                # @param [Array<Symbol>] category names
         | 
| 27 | 
            +
                def add(*category_names)
         | 
| 28 | 
            +
                  @redis.pipelined do
         | 
| 29 | 
            +
                    category_names.each { |category_name| hsetnx(category_name, 0) }
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                # Remove categories.
         | 
| 34 | 
            +
                #
         | 
| 35 | 
            +
                # @param [Array<Symbol>] category names
         | 
| 36 | 
            +
                def remove(*category_names)
         | 
| 37 | 
            +
                  @redis.pipelined do
         | 
| 38 | 
            +
                    category_names.each { |category_name| hdel(category_name) }
         | 
| 39 | 
            +
                    @redis.del(*category_names.map(&method(:make_category_key)))
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                include Enumerable
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                # Apply process for each category.
         | 
| 46 | 
            +
                #
         | 
| 47 | 
            +
                # @yield a block to be called for every category
         | 
| 48 | 
            +
                # @yieldparam [Inferx::Category] category
         | 
| 49 | 
            +
                def each
         | 
| 50 | 
            +
                  all.each { |category_name| yield spawn(Category, category_name) }
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
              end
         | 
| 53 | 
            +
            end
         | 
| @@ -0,0 +1,139 @@ | |
| 1 | 
            +
            require 'inferx/adapter'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class Inferx
         | 
| 4 | 
            +
              class Category < Adapter
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                # @param [Redis] an instance of Redis
         | 
| 7 | 
            +
                # @param [Symbol] a category name
         | 
| 8 | 
            +
                # @param [String] namespace of keys to be used to Redis
         | 
| 9 | 
            +
                def initialize(redis, name, namespace = nil)
         | 
| 10 | 
            +
                  super(redis, namespace)
         | 
| 11 | 
            +
                  @name = name
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                attr_reader :name
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                # Get words with scores in the category.
         | 
| 17 | 
            +
                #
         | 
| 18 | 
            +
                # @param [Hash] options
         | 
| 19 | 
            +
                #   - `:score => Integer`: lower limit for getting by score
         | 
| 20 | 
            +
                #   - `:rank  => Integer`: upper limit for getting by rank
         | 
| 21 | 
            +
                #
         | 
| 22 | 
            +
                # @return [Hash<String, Integer>] words with scores
         | 
| 23 | 
            +
                def all(options = {})
         | 
| 24 | 
            +
                  words_with_scores = if score = options[:score]
         | 
| 25 | 
            +
                                        zrevrangebyscore('+inf', score, :withscores => true)
         | 
| 26 | 
            +
                                      else
         | 
| 27 | 
            +
                                        rank = options[:rank] || -1
         | 
| 28 | 
            +
                                        zrevrange(0, rank, :withscores => true)
         | 
| 29 | 
            +
                                      end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  size = words_with_scores.size
         | 
| 32 | 
            +
                  index = 1
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  while index < size
         | 
| 35 | 
            +
                    words_with_scores[index] = words_with_scores[index].to_i
         | 
| 36 | 
            +
                    index += 2
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  Hash[*words_with_scores]
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                # Get score of a word.
         | 
| 43 | 
            +
                #
         | 
| 44 | 
            +
                # @param [String] a word
         | 
| 45 | 
            +
                # @return [Integer, nil]
         | 
| 46 | 
            +
                #   - when the word is member, score of the word
         | 
| 47 | 
            +
                #   - when the word is not member, nil
         | 
| 48 | 
            +
                def get(word)
         | 
| 49 | 
            +
                  score = zscore(word)
         | 
| 50 | 
            +
                  score ? score.to_i : nil
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
                alias [] get
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                # Enhance the training data giving words.
         | 
| 55 | 
            +
                #
         | 
| 56 | 
            +
                # @param [Array<String>] words
         | 
| 57 | 
            +
                def train(words)
         | 
| 58 | 
            +
                  @redis.pipelined do
         | 
| 59 | 
            +
                    increase = collect(words).inject(0) do |count, pair|
         | 
| 60 | 
            +
                      zincrby(pair[1], pair[0])
         | 
| 61 | 
            +
                      count + pair[1]
         | 
| 62 | 
            +
                    end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                    hincrby(name, increase) if increase > 0
         | 
| 65 | 
            +
                  end
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                # Attenuate the training data giving words.
         | 
| 69 | 
            +
                #
         | 
| 70 | 
            +
                # @param [Array<String>] words
         | 
| 71 | 
            +
                def untrain(words)
         | 
| 72 | 
            +
                  decrease = 0
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                  values = @redis.pipelined do
         | 
| 75 | 
            +
                    decrease = collect(words).inject(0) do |count, pair|
         | 
| 76 | 
            +
                      zincrby(-pair[1], pair[0])
         | 
| 77 | 
            +
                      count + pair[1]
         | 
| 78 | 
            +
                    end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                    zremrangebyscore('-inf', 0)
         | 
| 81 | 
            +
                  end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                  values[0..-2].each do |score|
         | 
| 84 | 
            +
                    score = score.to_i
         | 
| 85 | 
            +
                    decrease += score if score < 0
         | 
| 86 | 
            +
                  end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                  hincrby(name, -decrease) if decrease > 0
         | 
| 89 | 
            +
                end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                # Get total of scores.
         | 
| 92 | 
            +
                #
         | 
| 93 | 
            +
                # @return [Integer] total of scores
         | 
| 94 | 
            +
                def size
         | 
| 95 | 
            +
                  (hget(name) || 0).to_i
         | 
| 96 | 
            +
                end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                # Get effectively scores for each word.
         | 
| 99 | 
            +
                #
         | 
| 100 | 
            +
                # @param [Array<String>] words
         | 
| 101 | 
            +
                # @param [Hash<String, Integer>] words with scores prepared in advance for
         | 
| 102 | 
            +
                #   reduce access to Redis
         | 
| 103 | 
            +
                # @return [Array<Integer>] scores for each word
         | 
| 104 | 
            +
                def scores(words, words_with_scores = {})
         | 
| 105 | 
            +
                  scores = @redis.pipelined do
         | 
| 106 | 
            +
                    words.each do |word|
         | 
| 107 | 
            +
                      zscore(word) unless words_with_scores[word]
         | 
| 108 | 
            +
                    end
         | 
| 109 | 
            +
                  end
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                  index = 0
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                  next_score = lambda do
         | 
| 114 | 
            +
                    score = scores[index]
         | 
| 115 | 
            +
                    index += 1
         | 
| 116 | 
            +
                    score ? score.to_i : nil
         | 
| 117 | 
            +
                  end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                  words.map { |word| words_with_scores[word] || next_score[] }
         | 
| 120 | 
            +
                end
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                private
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                %w(zrevrange zrevrangebyscore zscore zincrby zremrangebyscore).each do |command|
         | 
| 125 | 
            +
                  define_method(command) do |*args|
         | 
| 126 | 
            +
                    @category_key ||= make_category_key(@name)
         | 
| 127 | 
            +
                    @redis.__send__(command, @category_key, *args)
         | 
| 128 | 
            +
                  end
         | 
| 129 | 
            +
                end
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                def collect(words)
         | 
| 132 | 
            +
                  words.inject({}) do |hash, word|
         | 
| 133 | 
            +
                    hash[word] ||= 0
         | 
| 134 | 
            +
                    hash[word] += 1
         | 
| 135 | 
            +
                    hash
         | 
| 136 | 
            +
                  end
         | 
| 137 | 
            +
                end
         | 
| 138 | 
            +
              end
         | 
| 139 | 
            +
            end
         | 
    
        data/lib/inferx.rb
    ADDED
    
    | @@ -0,0 +1,56 @@ | |
| 1 | 
            +
            require 'redis'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'inferx/version'
         | 
| 4 | 
            +
            require 'inferx/categories'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            class Inferx
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              # @param [Hash] options
         | 
| 9 | 
            +
              #   - `:namespace => String`: namespace of keys to be used to Redis
         | 
| 10 | 
            +
              #
         | 
| 11 | 
            +
              #   Other options are passed to Redis#initialize in:
         | 
| 12 | 
            +
              #     https://github.com/redis/redis-rb
         | 
| 13 | 
            +
              def initialize(options = {})
         | 
| 14 | 
            +
                namespace = options.delete(:namespace)
         | 
| 15 | 
            +
                redis = Redis.new(options)
         | 
| 16 | 
            +
                @categories = Categories.new(redis, namespace)
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              attr_reader :categories
         | 
| 20 | 
            +
             | 
| 21 | 
            +
              # Get a score of a category according to a set of words.
         | 
| 22 | 
            +
              #
         | 
| 23 | 
            +
              # @param [Inferx::Category] a category for scoring
         | 
| 24 | 
            +
              # @param [Array<String>] a set of words
         | 
| 25 | 
            +
              # @return [Float] a score of the category
         | 
| 26 | 
            +
              def score(category, words)
         | 
| 27 | 
            +
                size = category.size.to_f
         | 
| 28 | 
            +
                return -Float::INFINITY unless size > 0
         | 
| 29 | 
            +
                words_with_scores = category.all(:rank => 500)
         | 
| 30 | 
            +
                scores = category.scores(words, words_with_scores)
         | 
| 31 | 
            +
                scores.inject(0) { |s, score| s + Math.log((score || 0.1) / size) }
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
              # Get a score for each category according to a set of words.
         | 
| 35 | 
            +
              #
         | 
| 36 | 
            +
              # @param [Array<String>] a set of words
         | 
| 37 | 
            +
              # @return [Hash<Symbol, Float>] scores to key a category
         | 
| 38 | 
            +
              #
         | 
| 39 | 
            +
              # @see #score
         | 
| 40 | 
            +
              def classifications(words)
         | 
| 41 | 
            +
                words = words.uniq
         | 
| 42 | 
            +
                Hash[@categories.map { |category| [category.name, score(category, words)] }]
         | 
| 43 | 
            +
              end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
              # Classify words to any one category
         | 
| 46 | 
            +
              #
         | 
| 47 | 
            +
              # @param [Array<String>] a set of words
         | 
| 48 | 
            +
              # @return [Symbol] most high-scoring category name
         | 
| 49 | 
            +
              #
         | 
| 50 | 
            +
              # @see #score
         | 
| 51 | 
            +
              # @see #classifications
         | 
| 52 | 
            +
              def classify(words)
         | 
| 53 | 
            +
                category = classifications(words).max_by { |score| score[1] }
         | 
| 54 | 
            +
                category ? category[0] : nil
         | 
| 55 | 
            +
              end
         | 
| 56 | 
            +
            end
         | 
| @@ -0,0 +1,75 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
            require 'inferx/adapter'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            describe Inferx::Adapter, '#initialize' do
         | 
| 5 | 
            +
              it 'sets the instance of Redis to @redis' do
         | 
| 6 | 
            +
                redis = redis_stub
         | 
| 7 | 
            +
                adapter = described_class.new(redis)
         | 
| 8 | 
            +
                adapter.instance_eval { @redis }.should == redis
         | 
| 9 | 
            +
              end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              context 'with a namespace' do
         | 
| 12 | 
            +
                it 'sets the namespace to @namespace' do
         | 
| 13 | 
            +
                  adapter = described_class.new(redis_stub, 'example')
         | 
| 14 | 
            +
                  adapter.instance_eval { @namespace }.should == 'example'
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
            end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            describe Inferx::Adapter, '#categories_key' do
         | 
| 20 | 
            +
              it "calls #{described_class}#make_category_key" do
         | 
| 21 | 
            +
                adapter = described_class.new(redis_stub)
         | 
| 22 | 
            +
                adapter.should_receive(:make_categories_key)
         | 
| 23 | 
            +
                adapter.categories_key
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
              it "calls #{described_class}#make_category_key once in same instance" do
         | 
| 27 | 
            +
                adapter = described_class.new(redis_stub)
         | 
| 28 | 
            +
                adapter.should_receive(:make_categories_key).and_return('inferx:categories')
         | 
| 29 | 
            +
                adapter.categories_key
         | 
| 30 | 
            +
                adapter.categories_key
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
            end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            describe Inferx::Adapter, '#make_categories_key' do
         | 
| 35 | 
            +
              it 'returns the key for access to categories' do
         | 
| 36 | 
            +
                adapter = described_class.new(redis_stub)
         | 
| 37 | 
            +
                adapter.categories_key.should == 'inferx:categories'
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
              context 'with a namespace' do
         | 
| 41 | 
            +
                it 'returns the key included the namespace' do
         | 
| 42 | 
            +
                  adapter = described_class.new(redis_stub, 'example')
         | 
| 43 | 
            +
                  adapter.categories_key.should == 'inferx:example:categories'
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
              end
         | 
| 46 | 
            +
            end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            describe Inferx::Adapter, '#make_category_key' do
         | 
| 49 | 
            +
              it 'returns the key for access to to scores stored each by word' do
         | 
| 50 | 
            +
                adapter = described_class.new(redis_stub)
         | 
| 51 | 
            +
                adapter.make_category_key(:red).should == 'inferx:categories:red'
         | 
| 52 | 
            +
              end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
              context 'with a namespace' do
         | 
| 55 | 
            +
                it 'returns the key included the namespace' do
         | 
| 56 | 
            +
                  adapter = described_class.new(redis_stub, 'example')
         | 
| 57 | 
            +
                  adapter.make_category_key(:red).should == 'inferx:example:categories:red'
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
              end
         | 
| 60 | 
            +
            end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
            describe Inferx::Adapter, '#spawn' do
         | 
| 63 | 
            +
              it 'calls constructor of the class with the instance variables and the arguments' do
         | 
| 64 | 
            +
                redis = redis_stub
         | 
| 65 | 
            +
                adapter = described_class.new(redis, 'example')
         | 
| 66 | 
            +
                klass = mock.tap { |m| m.should_receive(:new).with(redis, 'arg1', 'arg2', 'example') }
         | 
| 67 | 
            +
                adapter.spawn(klass, 'arg1', 'arg2')
         | 
| 68 | 
            +
              end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
              it 'returns an instance of the class' do
         | 
| 71 | 
            +
                adapter = described_class.new(redis_stub)
         | 
| 72 | 
            +
                klass = stub.tap { |s| s.stub!(:new).and_return('klass') }
         | 
| 73 | 
            +
                adapter.spawn(klass).should == 'klass'
         | 
| 74 | 
            +
              end
         | 
| 75 | 
            +
            end
         | 
| @@ -0,0 +1,151 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
            require 'inferx/categories'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            describe Inferx::Categories do
         | 
| 5 | 
            +
              it 'includes Enumerable' do
         | 
| 6 | 
            +
                described_class.should be_include(Enumerable)
         | 
| 7 | 
            +
              end
         | 
| 8 | 
            +
            end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            describe Inferx::Categories, '#initialize' do
         | 
| 11 | 
            +
              it 'calls Inferx::Adapter#initialize' do
         | 
| 12 | 
            +
                redis = redis_stub
         | 
| 13 | 
            +
                categories = described_class.new(redis, 'example')
         | 
| 14 | 
            +
                categories.instance_eval { @redis }.should == redis
         | 
| 15 | 
            +
                categories.instance_eval { @namespace }.should == 'example'
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
            end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            describe Inferx::Categories, '#all' do
         | 
| 20 | 
            +
              it 'calls Redis#hkeys' do
         | 
| 21 | 
            +
                redis = redis_stub do |s|
         | 
| 22 | 
            +
                  s.should_receive(:hkeys).with('inferx:categories')
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                categories = described_class.new(redis)
         | 
| 26 | 
            +
                categories.all
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
              it 'returns the all categories as Symbol' do
         | 
| 30 | 
            +
                redis = redis_stub do |s|
         | 
| 31 | 
            +
                  s.stub!(:hkeys).and_return(%w(red green blue))
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                categories = described_class.new(redis)
         | 
| 35 | 
            +
                categories.all.should == [:red, :green, :blue]
         | 
| 36 | 
            +
              end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
              it 'returns an empty array if the key is missing' do
         | 
| 39 | 
            +
                redis = redis_stub do |s|
         | 
| 40 | 
            +
                  s.stub!(:hkeys).and_return([])
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                categories = described_class.new(redis)
         | 
| 44 | 
            +
                categories.all.should be_empty
         | 
| 45 | 
            +
              end
         | 
| 46 | 
            +
            end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            describe Inferx::Categories, '#get' do
         | 
| 49 | 
            +
              it 'calls Redis#hexists' do
         | 
| 50 | 
            +
                redis = redis_stub do |s|
         | 
| 51 | 
            +
                  s.should_receive(:hexists).with('inferx:categories', :red).and_return(true)
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                categories = described_class.new(redis)
         | 
| 55 | 
            +
                categories.get(:red)
         | 
| 56 | 
            +
              end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
              it 'calles Inferx::Category.new with the instance of Redis, the category name and the namepsace' do
         | 
| 59 | 
            +
                redis = redis_stub do |s|
         | 
| 60 | 
            +
                  s.stub!(:hexists).and_return(true)
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                Inferx::Category.should_receive(:new).with(redis, :red, 'example')
         | 
| 64 | 
            +
                categories = described_class.new(redis, 'example')
         | 
| 65 | 
            +
                categories.get(:red)
         | 
| 66 | 
            +
              end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
              it 'returns an instance of Inferx::Category' do
         | 
| 69 | 
            +
                redis = redis_stub do |s|
         | 
| 70 | 
            +
                  s.stub!(:hexists).and_return(true)
         | 
| 71 | 
            +
                end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                categories = described_class.new(redis)
         | 
| 74 | 
            +
                categories.get(:red).should be_an(Inferx::Category)
         | 
| 75 | 
            +
              end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
              context 'with a missing category' do
         | 
| 78 | 
            +
                it 'raises ArgumentError' do
         | 
| 79 | 
            +
                  redis = redis_stub do |s|
         | 
| 80 | 
            +
                    s.stub!(:hexists).and_return(false)
         | 
| 81 | 
            +
                  end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                  categories = described_class.new(redis)
         | 
| 84 | 
            +
                  lambda { categories.get(:red) }.should raise_error(ArgumentError, /'red' is missing/)
         | 
| 85 | 
            +
                end
         | 
| 86 | 
            +
              end
         | 
| 87 | 
            +
            end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
            describe Inferx::Categories, '#add' do
         | 
| 90 | 
            +
              it 'calls Redis#hsetnx' do
         | 
| 91 | 
            +
                redis = redis_stub do |s|
         | 
| 92 | 
            +
                  s.should_receive(:hsetnx).with('inferx:categories', :red, 0)
         | 
| 93 | 
            +
                end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                categories = described_class.new(redis)
         | 
| 96 | 
            +
                categories.add(:red)
         | 
| 97 | 
            +
              end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
              it 'calls Redis#hsetnx according to the number of the categories' do
         | 
| 100 | 
            +
                redis = redis_stub do |s|
         | 
| 101 | 
            +
                  s.should_receive(:hsetnx).with('inferx:categories', :red, 0)
         | 
| 102 | 
            +
                  s.should_receive(:hsetnx).with('inferx:categories', :green, 0)
         | 
| 103 | 
            +
                end
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                categories = described_class.new(redis)
         | 
| 106 | 
            +
                categories.add(:red, :green)
         | 
| 107 | 
            +
              end
         | 
| 108 | 
            +
            end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
            describe Inferx::Categories, '#remove' do
         | 
| 111 | 
            +
              it 'calls Redis#hdel and Redis#del' do
         | 
| 112 | 
            +
                redis = redis_stub do |s|
         | 
| 113 | 
            +
                  s.should_receive(:hdel).with('inferx:categories', :red)
         | 
| 114 | 
            +
                  s.should_receive(:del).with('inferx:categories:red')
         | 
| 115 | 
            +
                end
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                categories = described_class.new(redis)
         | 
| 118 | 
            +
                categories.remove(:red)
         | 
| 119 | 
            +
              end
         | 
| 120 | 
            +
             | 
| 121 | 
            +
              it 'calls Redis#hdel according to the number of the categories' do
         | 
| 122 | 
            +
                redis = redis_stub do |s|
         | 
| 123 | 
            +
                  s.should_receive(:hdel).with('inferx:categories', :red)
         | 
| 124 | 
            +
                  s.should_receive(:hdel).with('inferx:categories', :green)
         | 
| 125 | 
            +
                  s.should_receive(:del).with('inferx:categories:red', 'inferx:categories:green')
         | 
| 126 | 
            +
                end
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                categories = described_class.new(redis)
         | 
| 129 | 
            +
                categories.remove(:red, :green)
         | 
| 130 | 
            +
              end
         | 
| 131 | 
            +
            end
         | 
| 132 | 
            +
             | 
| 133 | 
            +
            describe Inferx::Categories, '#each' do
         | 
| 134 | 
            +
              before do
         | 
| 135 | 
            +
                @redis = redis_stub do |s|
         | 
| 136 | 
            +
                  s.stub!(:hkeys).and_return(%w(red green blue))
         | 
| 137 | 
            +
                end
         | 
| 138 | 
            +
              end
         | 
| 139 | 
            +
             | 
| 140 | 
            +
              it 'passes an instance of Inferx::Category to the block' do
         | 
| 141 | 
            +
                categories = described_class.new(@redis)
         | 
| 142 | 
            +
                categories.each { |category| category.should be_an(Inferx::Category) }
         | 
| 143 | 
            +
              end
         | 
| 144 | 
            +
             | 
| 145 | 
            +
              it 'calls the block according to the number of the categories' do
         | 
| 146 | 
            +
                n = 0
         | 
| 147 | 
            +
                categories = described_class.new(@redis)
         | 
| 148 | 
            +
                categories.each { |category| n += 1 }
         | 
| 149 | 
            +
                n.should == 3
         | 
| 150 | 
            +
              end
         | 
| 151 | 
            +
            end
         | 
| @@ -0,0 +1,206 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
            require 'inferx/category'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            describe Inferx::Category, '#initialize' do
         | 
| 5 | 
            +
              it 'calls Inferx::Adapter#initialize' do
         | 
| 6 | 
            +
                redis = redis_stub
         | 
| 7 | 
            +
                category = described_class.new(redis, :red, 'example')
         | 
| 8 | 
            +
                category.instance_eval { @redis }.should == redis
         | 
| 9 | 
            +
                category.instance_eval { @namespace }.should == 'example'
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              it 'sets the category name to the name attribute' do
         | 
| 13 | 
            +
                category = described_class.new(redis_stub, :red)
         | 
| 14 | 
            +
                category.name.should == :red
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
            end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            describe Inferx::Category, '#all' do
         | 
| 19 | 
            +
              it 'calls Redis#revrange' do
         | 
| 20 | 
            +
                redis = redis_stub do |s|
         | 
| 21 | 
            +
                  s.should_receive(:zrevrange).with('inferx:categories:red', 0, -1, :withscores => true).and_return([])
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                category = described_class.new(redis, :red)
         | 
| 25 | 
            +
                category.all
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              it 'returns the words with the score' do
         | 
| 29 | 
            +
                redis = redis_stub do |s|
         | 
| 30 | 
            +
                  s.stub!(:zrevrange).with('inferx:categories:red', 0, -1, :withscores => true).and_return(%w(apple 2 strawberry 3))
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                category = described_class.new(redis, :red)
         | 
| 34 | 
            +
                category.all.should == {'apple' => 2, 'strawberry' => 3}
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
              context 'with the rank option' do
         | 
| 38 | 
            +
                it 'calls Redis#zrevrange' do
         | 
| 39 | 
            +
                  redis = redis_stub do |s|
         | 
| 40 | 
            +
                    s.should_receive(:zrevrange).with('inferx:categories:red', 0, 1000, :withscores => true).and_return([])
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  category = described_class.new(redis, :red)
         | 
| 44 | 
            +
                  category.all(:rank => 1000)
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
              end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
              context 'with the score option' do
         | 
| 49 | 
            +
                it 'calls Redis#zrevrangebyscore' do
         | 
| 50 | 
            +
                  redis = redis_stub do |s|
         | 
| 51 | 
            +
                    s.should_receive(:zrevrangebyscore).with('inferx:categories:red', '+inf', 2, :withscores => true).and_return([])
         | 
| 52 | 
            +
                  end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                  category = described_class.new(redis, :red)
         | 
| 55 | 
            +
                  category.all(:score => 2)
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
              end
         | 
| 58 | 
            +
            end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
            describe Inferx::Category, '#get' do
         | 
| 61 | 
            +
              it 'calls Redis#zscore' do
         | 
| 62 | 
            +
                redis = redis_stub do |s|
         | 
| 63 | 
            +
                  s.should_receive(:zscore).with('inferx:categories:red', 'apple')
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                category = described_class.new(redis, :red)
         | 
| 67 | 
            +
                category.get('apple')
         | 
| 68 | 
            +
              end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
              it 'returns the score as Integer' do
         | 
| 71 | 
            +
                redis = redis_stub do |s|
         | 
| 72 | 
            +
                  s.stub!(:zscore).with('inferx:categories:red', 'apple').and_return('1')
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                category = described_class.new(redis, :red)
         | 
| 76 | 
            +
                category.get('apple').should == 1
         | 
| 77 | 
            +
              end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
              context 'with a missing word' do
         | 
| 80 | 
            +
                it 'returns nil' do
         | 
| 81 | 
            +
                  redis = redis_stub do |s|
         | 
| 82 | 
            +
                    s.stub!(:zscore).with('inferx:categories:red', 'strawberry').and_return(nil)
         | 
| 83 | 
            +
                  end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                  category = described_class.new(redis, :red)
         | 
| 86 | 
            +
                  category.get('strawberry').should be_nil
         | 
| 87 | 
            +
                end
         | 
| 88 | 
            +
              end
         | 
| 89 | 
            +
            end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
            describe Inferx::Category, '#train' do
         | 
| 92 | 
            +
              it 'calls Redis#zincrby and Redis#hincrby' do
         | 
| 93 | 
            +
                redis = redis_stub do |s|
         | 
| 94 | 
            +
                  s.should_receive(:zincrby).with('inferx:categories:red', 2, 'apple')
         | 
| 95 | 
            +
                  s.should_receive(:zincrby).with('inferx:categories:red', 3, 'strawberry')
         | 
| 96 | 
            +
                  s.should_receive(:hincrby).with('inferx:categories', :red, 5)
         | 
| 97 | 
            +
                end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                category = described_class.new(redis, :red)
         | 
| 100 | 
            +
                category.train(%w(apple strawberry apple strawberry strawberry))
         | 
| 101 | 
            +
              end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
              context 'with no update' do
         | 
| 104 | 
            +
                it 'does not call Redis#hincrby' do
         | 
| 105 | 
            +
                  redis = redis_stub do |s|
         | 
| 106 | 
            +
                    s.should_not_receive(:hincrby)
         | 
| 107 | 
            +
                  end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                  category = described_class.new(redis, :red)
         | 
| 110 | 
            +
                  category.train(%w())
         | 
| 111 | 
            +
                end
         | 
| 112 | 
            +
              end
         | 
| 113 | 
            +
            end
         | 
| 114 | 
            +
             | 
| 115 | 
            +
            describe Inferx::Category, '#untrain' do
         | 
| 116 | 
            +
              it 'calls Redis#zincrby, Redis#zremrangebyscore and Redis#hincrby' do
         | 
| 117 | 
            +
                redis = redis_stub do |s|
         | 
| 118 | 
            +
                  s.should_receive(:zincrby).with('inferx:categories:red', -2, 'apple')
         | 
| 119 | 
            +
                  s.should_receive(:zincrby).with('inferx:categories:red', -3, 'strawberry')
         | 
| 120 | 
            +
                  s.should_receive(:zremrangebyscore).with('inferx:categories:red', '-inf', 0).and_return(%w(3 -2 1))
         | 
| 121 | 
            +
                  s.should_receive(:hincrby).with('inferx:categories', :red, -3)
         | 
| 122 | 
            +
                end
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                category = described_class.new(redis, :red)
         | 
| 125 | 
            +
                category.untrain(%w(apple strawberry apple strawberry strawberry))
         | 
| 126 | 
            +
              end
         | 
| 127 | 
            +
             | 
| 128 | 
            +
              context 'with no update' do
         | 
| 129 | 
            +
                it 'does not call Redis#hincrby' do
         | 
| 130 | 
            +
                  redis = redis_stub do |s|
         | 
| 131 | 
            +
                    s.stub!(:zincrby)
         | 
| 132 | 
            +
                    s.stub!(:zremrangebyscore).and_return(%w(-2 -3 2))
         | 
| 133 | 
            +
                    s.should_not_receive(:hincrby)
         | 
| 134 | 
            +
                  end
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                  category = described_class.new(redis, :red)
         | 
| 137 | 
            +
                  category.untrain(%w(apple strawberry apple strawberry strawberry))
         | 
| 138 | 
            +
                end
         | 
| 139 | 
            +
              end
         | 
| 140 | 
            +
            end
         | 
| 141 | 
            +
             | 
| 142 | 
            +
            describe Inferx::Category, '#size' do
         | 
| 143 | 
            +
              it 'calls Redis#hget' do
         | 
| 144 | 
            +
                redis = redis_stub do |s|
         | 
| 145 | 
            +
                  s.should_receive(:hget).with('inferx:categories', :red)
         | 
| 146 | 
            +
                end
         | 
| 147 | 
            +
             | 
| 148 | 
            +
                category = described_class.new(redis, :red)
         | 
| 149 | 
            +
                category.size
         | 
| 150 | 
            +
              end
         | 
| 151 | 
            +
             | 
| 152 | 
            +
              it 'returns total of the score of the words as Integer' do
         | 
| 153 | 
            +
                redis = redis_stub do |s|
         | 
| 154 | 
            +
                  s.stub!(:hget).and_return('1')
         | 
| 155 | 
            +
                end
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                category = described_class.new(redis, :red)
         | 
| 158 | 
            +
                category.size.should == 1
         | 
| 159 | 
            +
              end
         | 
| 160 | 
            +
             | 
| 161 | 
            +
              context 'with the missing key' do
         | 
| 162 | 
            +
                it 'returns 0' do
         | 
| 163 | 
            +
                  redis = redis_stub do |s|
         | 
| 164 | 
            +
                    s.stub!(:hget).and_return(nil)
         | 
| 165 | 
            +
                  end
         | 
| 166 | 
            +
             | 
| 167 | 
            +
                  category = described_class.new(redis, :red)
         | 
| 168 | 
            +
                  category.size.should == 0
         | 
| 169 | 
            +
                end
         | 
| 170 | 
            +
              end
         | 
| 171 | 
            +
            end
         | 
| 172 | 
            +
             | 
| 173 | 
            +
            describe Inferx::Category, '#scores' do
         | 
| 174 | 
            +
              it 'calls Redis#zscore' do
         | 
| 175 | 
            +
                redis = redis_stub do |s|
         | 
| 176 | 
            +
                  s.should_receive(:zscore).with('inferx:categories:red', 'apple')
         | 
| 177 | 
            +
                  s.should_receive(:zscore).with('inferx:categories:red', 'strawberry')
         | 
| 178 | 
            +
                end
         | 
| 179 | 
            +
             | 
| 180 | 
            +
                category = described_class.new(redis, :red)
         | 
| 181 | 
            +
                category.scores(%w(apple strawberry))
         | 
| 182 | 
            +
              end
         | 
| 183 | 
            +
             | 
| 184 | 
            +
              it 'returns the scores' do
         | 
| 185 | 
            +
                redis = redis_stub do |s|
         | 
| 186 | 
            +
                  s.stub!(:pipelined).and_return(%w(2 3))
         | 
| 187 | 
            +
                end
         | 
| 188 | 
            +
             | 
| 189 | 
            +
                category = described_class.new(redis, :red)
         | 
| 190 | 
            +
                scores = category.scores(%w(apple strawberry))
         | 
| 191 | 
            +
                scores.should == [2, 3]
         | 
| 192 | 
            +
              end
         | 
| 193 | 
            +
             | 
| 194 | 
            +
              context 'with words with scores' do
         | 
| 195 | 
            +
                it 'returns the scores to use the cache' do
         | 
| 196 | 
            +
                  redis = redis_stub do |s|
         | 
| 197 | 
            +
                    s.should_not_receive(:zscore).with('inferx:categories:red', 'strawberry')
         | 
| 198 | 
            +
                    s.stub!(:pipelined).and_return { |&block| block.call; [2] }
         | 
| 199 | 
            +
                  end
         | 
| 200 | 
            +
             | 
| 201 | 
            +
                  category = described_class.new(redis, :red)
         | 
| 202 | 
            +
                  scores = category.scores(%w(apple strawberry), 'strawberry' => 3, 'hoge' => 1)
         | 
| 203 | 
            +
                  scores.should == [2, 3]
         | 
| 204 | 
            +
                end
         | 
| 205 | 
            +
              end
         | 
| 206 | 
            +
            end
         | 
    
        data/spec/inferx_spec.rb
    ADDED
    
    | @@ -0,0 +1,137 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
            require 'inferx'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            describe Inferx do
         | 
| 5 | 
            +
              it 'is an instance of Class' do
         | 
| 6 | 
            +
                described_class.should be_an_instance_of(Class)
         | 
| 7 | 
            +
              end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              it 'has VERSION constant' do
         | 
| 10 | 
            +
                described_class.should be_const_defined(:VERSION)
         | 
| 11 | 
            +
              end
         | 
| 12 | 
            +
            end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            describe Inferx, '#initialize' do
         | 
| 15 | 
            +
              it "calls #{described_class}::Categories.new with a connection of Redis and the namespace option" do
         | 
| 16 | 
            +
                redis = redis_stub
         | 
| 17 | 
            +
                Inferx::Categories.should_receive(:new).with(redis, 'example')
         | 
| 18 | 
            +
                described_class.new(:namespace => 'example')
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
              it "sets an instance of #{described_class}::Categories to the categories attribute" do
         | 
| 22 | 
            +
                redis_stub
         | 
| 23 | 
            +
                inferx = described_class.new
         | 
| 24 | 
            +
                inferx.categories.should be_an(Inferx::Categories)
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
            end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            describe Inferx, '#score' do
         | 
| 29 | 
            +
              before do
         | 
| 30 | 
            +
                @inferx = described_class.new
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
              it "calls #{described_class}::Categories#size, #{described_class}::Category#all and #{described_class}::Category#scores" do
         | 
| 34 | 
            +
                category = mock.tap do |m|
         | 
| 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])
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                @inferx.score(category, %w(apple))
         | 
| 41 | 
            +
              end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
              it 'returns an expected score' do
         | 
| 44 | 
            +
                {
         | 
| 45 | 
            +
                  [%w(apple), 2, [2]]   =>  0.0,
         | 
| 46 | 
            +
                  [%w(apple), 2, [nil]] => -2.995732273553991,
         | 
| 47 | 
            +
                  [%w(apple), 3, [nil]] => -3.4011973816621555
         | 
| 48 | 
            +
                }.each do |args, expected|
         | 
| 49 | 
            +
                  words, size, scores = args
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  category = stub.tap do |s|
         | 
| 52 | 
            +
                    s.stub!(:size).and_return(size)
         | 
| 53 | 
            +
                    s.stub!(:scores).and_return(scores)
         | 
| 54 | 
            +
                    s.stub!(:all)
         | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                  @inferx.score(category, words).should == expected
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
              end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
              it 'returns a negative infinity number if the category does not have words' do
         | 
| 62 | 
            +
                category = stub.tap do |s|
         | 
| 63 | 
            +
                  s.stub!(:size).and_return(0)
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                score = @inferx.score(category, %w(apple))
         | 
| 67 | 
            +
                score.should be_infinite
         | 
| 68 | 
            +
                score.should < 0
         | 
| 69 | 
            +
              end
         | 
| 70 | 
            +
            end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
            describe Inferx, '#classifications' do
         | 
| 73 | 
            +
              before do
         | 
| 74 | 
            +
                categories = [:red, :green, :blue].map do |category_name|
         | 
| 75 | 
            +
                  stub.tap { |s| s.stub!(:name).and_return(category_name) }
         | 
| 76 | 
            +
                end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                @inferx = described_class.new.tap do |s|
         | 
| 79 | 
            +
                  s.instance_eval { @categories = categories }
         | 
| 80 | 
            +
                  s.stub!(:score).and_return { |category, words| "score of #{category.name}" }
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
              end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
              it "calls #{described_class}#score" do
         | 
| 85 | 
            +
                @inferx.tap do |m|
         | 
| 86 | 
            +
                  m.should_receive(:score).with(@inferx.categories[0], %w(apple))
         | 
| 87 | 
            +
                  m.should_receive(:score).with(@inferx.categories[1], %w(apple))
         | 
| 88 | 
            +
                  m.should_receive(:score).with(@inferx.categories[2], %w(apple))
         | 
| 89 | 
            +
                end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                @inferx.classifications(%w(apple))
         | 
| 92 | 
            +
              end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
              it 'calls uniq method of the words' do
         | 
| 95 | 
            +
                words = %w(apple).tap do |m|
         | 
| 96 | 
            +
                  m.should_receive(:uniq).and_return(m)
         | 
| 97 | 
            +
                end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                @inferx.classifications(words)
         | 
| 100 | 
            +
              end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
              it 'returns the scores to key the category name' do
         | 
| 103 | 
            +
                @inferx.classifications(%w(apple)).should == {
         | 
| 104 | 
            +
                  :red   => 'score of red',
         | 
| 105 | 
            +
                  :green => 'score of green',
         | 
| 106 | 
            +
                  :blue  => 'score of blue'
         | 
| 107 | 
            +
                }
         | 
| 108 | 
            +
              end
         | 
| 109 | 
            +
            end
         | 
| 110 | 
            +
             | 
| 111 | 
            +
            describe Inferx, '#classify' do
         | 
| 112 | 
            +
              before do
         | 
| 113 | 
            +
                @inferx = described_class.new.tap do |s|
         | 
| 114 | 
            +
                  s.stub!(:classifications).and_return(:red => -2, :green => -1, :blue => -3)
         | 
| 115 | 
            +
                end
         | 
| 116 | 
            +
              end
         | 
| 117 | 
            +
             | 
| 118 | 
            +
              it "calls #{described_class}#classifications" do
         | 
| 119 | 
            +
                @inferx.tap do |m|
         | 
| 120 | 
            +
                  m.should_receive(:classifications).with(%w(apple)).and_return(:red => -2)
         | 
| 121 | 
            +
                end
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                @inferx.classify(%w(apple))
         | 
| 124 | 
            +
              end
         | 
| 125 | 
            +
             | 
| 126 | 
            +
              it 'returns the most high-scoring category' do
         | 
| 127 | 
            +
                @inferx.classify(%w(apple)).should == :green
         | 
| 128 | 
            +
              end
         | 
| 129 | 
            +
             | 
| 130 | 
            +
              it 'returns nil if the categories is nothing' do
         | 
| 131 | 
            +
                @inferx.tap do |s|
         | 
| 132 | 
            +
                  s.stub!(:classifications).and_return({})
         | 
| 133 | 
            +
                end
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                @inferx.classify(%w(apple)).should be_nil
         | 
| 136 | 
            +
              end
         | 
| 137 | 
            +
            end
         | 
    
        data/spec/spec_helper.rb
    ADDED
    
    
    
        metadata
    ADDED
    
    | @@ -0,0 +1,99 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification
         | 
| 2 | 
            +
            name: inferx
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            +
              version: 0.1.5
         | 
| 5 | 
            +
              prerelease: 
         | 
| 6 | 
            +
            platform: ruby
         | 
| 7 | 
            +
            authors:
         | 
| 8 | 
            +
            - Takahiro Kondo
         | 
| 9 | 
            +
            autorequire: 
         | 
| 10 | 
            +
            bindir: bin
         | 
| 11 | 
            +
            cert_chain: []
         | 
| 12 | 
            +
            date: 2012-04-02 00:00:00.000000000 Z
         | 
| 13 | 
            +
            dependencies:
         | 
| 14 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 15 | 
            +
              name: redis
         | 
| 16 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 17 | 
            +
                none: false
         | 
| 18 | 
            +
                requirements:
         | 
| 19 | 
            +
                - - ! '>='
         | 
| 20 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 21 | 
            +
                    version: '0'
         | 
| 22 | 
            +
              type: :runtime
         | 
| 23 | 
            +
              prerelease: false
         | 
| 24 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 25 | 
            +
                none: false
         | 
| 26 | 
            +
                requirements:
         | 
| 27 | 
            +
                - - ! '>='
         | 
| 28 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 29 | 
            +
                    version: '0'
         | 
| 30 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 31 | 
            +
              name: rspec
         | 
| 32 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 33 | 
            +
                none: false
         | 
| 34 | 
            +
                requirements:
         | 
| 35 | 
            +
                - - ! '>='
         | 
| 36 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 37 | 
            +
                    version: '0'
         | 
| 38 | 
            +
              type: :development
         | 
| 39 | 
            +
              prerelease: false
         | 
| 40 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 41 | 
            +
                none: false
         | 
| 42 | 
            +
                requirements:
         | 
| 43 | 
            +
                - - ! '>='
         | 
| 44 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 45 | 
            +
                    version: '0'
         | 
| 46 | 
            +
            description: It is Naive Bayes classifier, and the training data is kept always by
         | 
| 47 | 
            +
              Redis.
         | 
| 48 | 
            +
            email:
         | 
| 49 | 
            +
            - kondo@atedesign.net
         | 
| 50 | 
            +
            executables: []
         | 
| 51 | 
            +
            extensions: []
         | 
| 52 | 
            +
            extra_rdoc_files: []
         | 
| 53 | 
            +
            files:
         | 
| 54 | 
            +
            - .gitignore
         | 
| 55 | 
            +
            - Gemfile
         | 
| 56 | 
            +
            - LICENSE
         | 
| 57 | 
            +
            - README.md
         | 
| 58 | 
            +
            - Rakefile
         | 
| 59 | 
            +
            - inferx.gemspec
         | 
| 60 | 
            +
            - lib/inferx.rb
         | 
| 61 | 
            +
            - lib/inferx/adapter.rb
         | 
| 62 | 
            +
            - lib/inferx/categories.rb
         | 
| 63 | 
            +
            - lib/inferx/category.rb
         | 
| 64 | 
            +
            - lib/inferx/version.rb
         | 
| 65 | 
            +
            - spec/inferx/adapter_spec.rb
         | 
| 66 | 
            +
            - spec/inferx/categories_spec.rb
         | 
| 67 | 
            +
            - spec/inferx/category_spec.rb
         | 
| 68 | 
            +
            - spec/inferx_spec.rb
         | 
| 69 | 
            +
            - spec/spec_helper.rb
         | 
| 70 | 
            +
            homepage: ''
         | 
| 71 | 
            +
            licenses: []
         | 
| 72 | 
            +
            post_install_message: 
         | 
| 73 | 
            +
            rdoc_options: []
         | 
| 74 | 
            +
            require_paths:
         | 
| 75 | 
            +
            - lib
         | 
| 76 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 77 | 
            +
              none: false
         | 
| 78 | 
            +
              requirements:
         | 
| 79 | 
            +
              - - ! '>='
         | 
| 80 | 
            +
                - !ruby/object:Gem::Version
         | 
| 81 | 
            +
                  version: '0'
         | 
| 82 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 83 | 
            +
              none: false
         | 
| 84 | 
            +
              requirements:
         | 
| 85 | 
            +
              - - ! '>='
         | 
| 86 | 
            +
                - !ruby/object:Gem::Version
         | 
| 87 | 
            +
                  version: '0'
         | 
| 88 | 
            +
            requirements: []
         | 
| 89 | 
            +
            rubyforge_project: 
         | 
| 90 | 
            +
            rubygems_version: 1.8.21
         | 
| 91 | 
            +
            signing_key: 
         | 
| 92 | 
            +
            specification_version: 3
         | 
| 93 | 
            +
            summary: Naive Bayes classifier, the training data on Redis
         | 
| 94 | 
            +
            test_files:
         | 
| 95 | 
            +
            - spec/inferx/adapter_spec.rb
         | 
| 96 | 
            +
            - spec/inferx/categories_spec.rb
         | 
| 97 | 
            +
            - spec/inferx/category_spec.rb
         | 
| 98 | 
            +
            - spec/inferx_spec.rb
         | 
| 99 | 
            +
            - spec/spec_helper.rb
         |