predictor 1.0.0 → 2.0.0.rc1
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.
- checksums.yaml +4 -4
- data/Changelog.md +13 -0
- data/Gemfile +1 -1
- data/README.md +75 -32
- data/docs/READMEv1.md +206 -0
- data/lib/predictor/base.rb +128 -60
- data/lib/predictor/input_matrix.rb +29 -82
- data/lib/predictor/version.rb +1 -1
- data/spec/base_spec.rb +160 -94
- data/spec/input_matrix_spec.rb +30 -160
- data/spec/predictor_spec.rb +1 -1
- metadata +6 -4
| @@ -3,6 +3,10 @@ class Predictor::InputMatrix | |
| 3 3 | 
             
                @opts = opts
         | 
| 4 4 | 
             
              end
         | 
| 5 5 |  | 
| 6 | 
            +
              def parent_redis_key(*append)
         | 
| 7 | 
            +
                ([@opts.fetch(:redis_prefix)] + append).flatten.compact.join(":")
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
             | 
| 6 10 | 
             
              def redis_key(*append)
         | 
| 7 11 | 
             
                ([@opts.fetch(:redis_prefix), @opts.fetch(:key)] + append).flatten.compact.join(":")
         | 
| 8 12 | 
             
              end
         | 
| @@ -11,30 +15,19 @@ class Predictor::InputMatrix | |
| 11 15 | 
             
                (@opts[:weight] || 1).to_f
         | 
| 12 16 | 
             
              end
         | 
| 13 17 |  | 
| 14 | 
            -
              def  | 
| 15 | 
            -
                 | 
| 16 | 
            -
                  item_ids.each { |item| add_single_nomulti(set_id, item) }
         | 
| 17 | 
            -
                end
         | 
| 18 | 
            -
              end
         | 
| 19 | 
            -
             | 
| 20 | 
            -
              def add_set!(set_id, item_ids)
         | 
| 21 | 
            -
                add_set(set_id, item_ids)
         | 
| 22 | 
            -
                item_ids.each { |item_id| process_item!(item_id) }
         | 
| 23 | 
            -
              end
         | 
| 24 | 
            -
             | 
| 25 | 
            -
              def add_single(set_id, item_id)
         | 
| 18 | 
            +
              def add_to_set(set, *items)
         | 
| 19 | 
            +
                items = items.flatten if items.count == 1 && items[0].is_a?(Array)
         | 
| 26 20 | 
             
                Predictor.redis.multi do
         | 
| 27 | 
            -
                  add_single_nomulti( | 
| 21 | 
            +
                  items.each { |item| add_single_nomulti(set, item) }
         | 
| 28 22 | 
             
                end
         | 
| 29 23 | 
             
              end
         | 
| 30 24 |  | 
| 31 | 
            -
              def  | 
| 32 | 
            -
                 | 
| 33 | 
            -
                process_item!(item_id)
         | 
| 25 | 
            +
              def add_set(set, items)
         | 
| 26 | 
            +
                add_to_set(set, *items)
         | 
| 34 27 | 
             
              end
         | 
| 35 28 |  | 
| 36 | 
            -
              def  | 
| 37 | 
            -
                 | 
| 29 | 
            +
              def add_single(set, item)
         | 
| 30 | 
            +
                add_to_set(set, item)
         | 
| 38 31 | 
             
              end
         | 
| 39 32 |  | 
| 40 33 | 
             
              def items_for(set)
         | 
| @@ -45,81 +38,26 @@ class Predictor::InputMatrix | |
| 45 38 | 
             
                Predictor.redis.sunion redis_key(:sets, item)
         | 
| 46 39 | 
             
              end
         | 
| 47 40 |  | 
| 48 | 
            -
              def related_items( | 
| 49 | 
            -
                sets = Predictor.redis.smembers(redis_key(:sets,  | 
| 41 | 
            +
              def related_items(item)
         | 
| 42 | 
            +
                sets = Predictor.redis.smembers(redis_key(:sets, item))
         | 
| 50 43 | 
             
                keys = sets.map { |set| redis_key(:items, set) }
         | 
| 51 | 
            -
                 | 
| 52 | 
            -
                  Predictor.redis.sunion(keys) - [item_id]
         | 
| 53 | 
            -
                else
         | 
| 54 | 
            -
                  []
         | 
| 55 | 
            -
                end
         | 
| 56 | 
            -
              end
         | 
| 57 | 
            -
             | 
| 58 | 
            -
              def similarity(item1, item2)
         | 
| 59 | 
            -
                Predictor.redis.zscore(redis_key(:similarities, item1), item2)
         | 
| 60 | 
            -
              end
         | 
| 61 | 
            -
             | 
| 62 | 
            -
              # calculate all similarities to other items in the matrix for item1
         | 
| 63 | 
            -
              def similarities_for(item1, with_scores: false, offset: 0, limit: -1)
         | 
| 64 | 
            -
                Predictor.redis.zrevrange(redis_key(:similarities, item1), offset, limit == -1 ? limit : offset + (limit - 1), with_scores: with_scores)
         | 
| 65 | 
            -
              end
         | 
| 66 | 
            -
             | 
| 67 | 
            -
              def process_item!(item)
         | 
| 68 | 
            -
                cache_similarities_for(item)
         | 
| 69 | 
            -
              end
         | 
| 70 | 
            -
             | 
| 71 | 
            -
              def process!
         | 
| 72 | 
            -
                all_items.each do |item|
         | 
| 73 | 
            -
                  process_item!(item)
         | 
| 74 | 
            -
                end
         | 
| 44 | 
            +
                keys.length > 0 ? Predictor.redis.sunion(keys) - [item] : []
         | 
| 75 45 | 
             
              end
         | 
| 76 46 |  | 
| 77 | 
            -
              # delete  | 
| 78 | 
            -
              def delete_item | 
| 79 | 
            -
                Predictor.redis. | 
| 80 | 
            -
             | 
| 81 | 
            -
                  sets = Predictor.redis.smembers(redis_key(:sets, item_id))
         | 
| 82 | 
            -
                  items = Predictor.redis.zrange(redis_key(:similarities, item_id), 0, -1)
         | 
| 47 | 
            +
              # delete item from the matrix
         | 
| 48 | 
            +
              def delete_item(item)
         | 
| 49 | 
            +
                Predictor.redis.watch(redis_key(:sets, item)) do
         | 
| 50 | 
            +
                  sets = Predictor.redis.smembers(redis_key(:sets, item))
         | 
| 83 51 | 
             
                  Predictor.redis.multi do |multi|
         | 
| 84 52 | 
             
                    sets.each do |set|
         | 
| 85 | 
            -
                      multi.srem(redis_key(:items, set),  | 
| 53 | 
            +
                      multi.srem(redis_key(:items, set), item)
         | 
| 86 54 | 
             
                    end
         | 
| 87 55 |  | 
| 88 | 
            -
                     | 
| 89 | 
            -
                      multi.zrem(redis_key(:similarities, item), item_id)
         | 
| 90 | 
            -
                    end
         | 
| 91 | 
            -
             | 
| 92 | 
            -
                    multi.del redis_key(:sets, item_id), redis_key(:similarities, item_id)
         | 
| 56 | 
            +
                    multi.del redis_key(:sets, item)
         | 
| 93 57 | 
             
                  end
         | 
| 94 58 | 
             
                end
         | 
| 95 59 | 
             
              end
         | 
| 96 60 |  | 
| 97 | 
            -
              private
         | 
| 98 | 
            -
             | 
| 99 | 
            -
              def add_single_nomulti(set_id, item_id)
         | 
| 100 | 
            -
                Predictor.redis.sadd(redis_key(:all_items), item_id)
         | 
| 101 | 
            -
                Predictor.redis.sadd(redis_key(:items, set_id), item_id)
         | 
| 102 | 
            -
                # add the set_id to the item_id's set--inverting the sets
         | 
| 103 | 
            -
                Predictor.redis.sadd(redis_key(:sets, item_id), set_id)
         | 
| 104 | 
            -
              end
         | 
| 105 | 
            -
             | 
| 106 | 
            -
              def cache_similarity(item1, item2)
         | 
| 107 | 
            -
                score = calculate_jaccard(item1, item2)
         | 
| 108 | 
            -
             | 
| 109 | 
            -
                if score > 0
         | 
| 110 | 
            -
                  Predictor.redis.multi do |multi|
         | 
| 111 | 
            -
                    multi.zadd(redis_key(:similarities, item1), score, item2)
         | 
| 112 | 
            -
                    multi.zadd(redis_key(:similarities, item2), score, item1)
         | 
| 113 | 
            -
                  end
         | 
| 114 | 
            -
                end
         | 
| 115 | 
            -
              end
         | 
| 116 | 
            -
             | 
| 117 | 
            -
              def cache_similarities_for(item)
         | 
| 118 | 
            -
                related_items(item).each do |related_item|
         | 
| 119 | 
            -
                  cache_similarity(item, related_item)
         | 
| 120 | 
            -
                end
         | 
| 121 | 
            -
              end
         | 
| 122 | 
            -
             | 
| 123 61 | 
             
              def calculate_jaccard(item1, item2)
         | 
| 124 62 | 
             
                x = nil
         | 
| 125 63 | 
             
                y = nil
         | 
| @@ -135,4 +73,13 @@ class Predictor::InputMatrix | |
| 135 73 | 
             
                  return 0.0
         | 
| 136 74 | 
             
                end
         | 
| 137 75 | 
             
              end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
              private
         | 
| 78 | 
            +
             | 
| 79 | 
            +
              def add_single_nomulti(set, item)
         | 
| 80 | 
            +
                Predictor.redis.sadd(parent_redis_key(:all_items), item)
         | 
| 81 | 
            +
                Predictor.redis.sadd(redis_key(:items, set), item)
         | 
| 82 | 
            +
                # add the set to the item's set--inverting the sets
         | 
| 83 | 
            +
                Predictor.redis.sadd(redis_key(:sets, item), set)
         | 
| 84 | 
            +
              end
         | 
| 138 85 | 
             
            end
         | 
    
        data/lib/predictor/version.rb
    CHANGED
    
    
    
        data/spec/base_spec.rb
    CHANGED
    
    | @@ -8,6 +8,7 @@ describe Predictor::Base do | |
| 8 8 | 
             
              before(:each) do
         | 
| 9 9 | 
             
                flush_redis!
         | 
| 10 10 | 
             
                BaseRecommender.input_matrices = {}
         | 
| 11 | 
            +
                BaseRecommender.limit_similarities_to(nil)
         | 
| 11 12 | 
             
              end
         | 
| 12 13 |  | 
| 13 14 | 
             
              describe "configuration" do
         | 
| @@ -16,6 +17,11 @@ describe Predictor::Base do | |
| 16 17 | 
             
                  BaseRecommender.input_matrices.keys.should == [:myinput]
         | 
| 17 18 | 
             
                end
         | 
| 18 19 |  | 
| 20 | 
            +
                it "should allow a similarity limit" do
         | 
| 21 | 
            +
                  BaseRecommender.limit_similarities_to(100)
         | 
| 22 | 
            +
                  BaseRecommender.similarity_limit.should == 100
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 19 25 | 
             
                it "should retrieve an input_matrix on a new instance" do
         | 
| 20 26 | 
             
                  BaseRecommender.input_matrix(:myinput)
         | 
| 21 27 | 
             
                  sm = BaseRecommender.new
         | 
| @@ -37,67 +43,56 @@ describe Predictor::Base do | |
| 37 43 | 
             
                end
         | 
| 38 44 | 
             
              end
         | 
| 39 45 |  | 
| 40 | 
            -
              describe " | 
| 41 | 
            -
                it " | 
| 42 | 
            -
                  BaseRecommender.input_matrix(: | 
| 43 | 
            -
                  BaseRecommender.input_matrix(: | 
| 44 | 
            -
                  sm = BaseRecommender.new
         | 
| 45 | 
            -
                  sm.myfirstinput.should_receive(:process_item!).with("fnorditem").and_return([["fooitem",0.5]])
         | 
| 46 | 
            -
                  sm.mysecondinput.should_receive(:process_item!).with("fnorditem").and_return([["fooitem",0.5]])
         | 
| 47 | 
            -
                  sm.process_item!("fnorditem")
         | 
| 48 | 
            -
                end
         | 
| 49 | 
            -
             | 
| 50 | 
            -
                it "should call process_item! on each input_matrix and add all outputs to the similarity matrix" do
         | 
| 51 | 
            -
                  BaseRecommender.input_matrix(:myfirstinput)
         | 
| 52 | 
            -
                  BaseRecommender.input_matrix(:mysecondinput)
         | 
| 46 | 
            +
              describe "all_items" do
         | 
| 47 | 
            +
                it "returns all items across all matrices" do
         | 
| 48 | 
            +
                  BaseRecommender.input_matrix(:anotherinput)
         | 
| 49 | 
            +
                  BaseRecommender.input_matrix(:yetanotherinput)
         | 
| 53 50 | 
             
                  sm = BaseRecommender.new
         | 
| 54 | 
            -
                  sm. | 
| 55 | 
            -
                  sm. | 
| 56 | 
            -
                  sm. | 
| 51 | 
            +
                  sm.add_to_matrix(:anotherinput, 'a', "foo", "bar")
         | 
| 52 | 
            +
                  sm.add_to_matrix(:yetanotherinput, 'b', "fnord", "shmoo", "bar")
         | 
| 53 | 
            +
                  sm.all_items.should include('foo', 'bar', 'fnord', 'shmoo')
         | 
| 54 | 
            +
                  sm.all_items.length.should == 4
         | 
| 57 55 | 
             
                end
         | 
| 56 | 
            +
              end
         | 
| 58 57 |  | 
| 59 | 
            -
             | 
| 60 | 
            -
             | 
| 61 | 
            -
                  BaseRecommender.input_matrix(: | 
| 58 | 
            +
              describe "add_to_matrix" do
         | 
| 59 | 
            +
                it "calls add_to_set on the given matrix" do
         | 
| 60 | 
            +
                  BaseRecommender.input_matrix(:anotherinput)
         | 
| 62 61 | 
             
                  sm = BaseRecommender.new
         | 
| 63 | 
            -
                  sm. | 
| 64 | 
            -
                  sm. | 
| 65 | 
            -
                  sm.process_item!("fnorditem")
         | 
| 62 | 
            +
                  sm.anotherinput.should_receive(:add_to_set).with('a', 'foo', 'bar')
         | 
| 63 | 
            +
                  sm.add_to_matrix(:anotherinput, 'a', 'foo', 'bar')
         | 
| 66 64 | 
             
                end
         | 
| 67 | 
            -
              end
         | 
| 68 65 |  | 
| 69 | 
            -
             | 
| 70 | 
            -
                it "should retrieve all items from all input matrices" do
         | 
| 66 | 
            +
                it "adds the items to the all_items storage" do
         | 
| 71 67 | 
             
                  BaseRecommender.input_matrix(:anotherinput)
         | 
| 72 | 
            -
                  BaseRecommender.input_matrix(:yetanotherinput)
         | 
| 73 68 | 
             
                  sm = BaseRecommender.new
         | 
| 74 | 
            -
                  sm.anotherinput | 
| 75 | 
            -
                  sm. | 
| 76 | 
            -
                  sm.all_items.length.should == 4
         | 
| 77 | 
            -
                  sm.all_items.should include("foo", "bar", "fnord", "shmoo")
         | 
| 69 | 
            +
                  sm.add_to_matrix(:anotherinput, 'a', 'foo', 'bar')
         | 
| 70 | 
            +
                  sm.all_items.should include('foo', 'bar')
         | 
| 78 71 | 
             
                end
         | 
| 72 | 
            +
              end
         | 
| 79 73 |  | 
| 80 | 
            -
             | 
| 74 | 
            +
              describe "add_to_matrix!" do
         | 
| 75 | 
            +
                it "calls add_to_matrix and process_items! for the given items" do
         | 
| 81 76 | 
             
                  BaseRecommender.input_matrix(:anotherinput)
         | 
| 82 | 
            -
                  BaseRecommender.input_matrix(:yetanotherinput)
         | 
| 83 77 | 
             
                  sm = BaseRecommender.new
         | 
| 84 | 
            -
                  sm. | 
| 85 | 
            -
                  sm. | 
| 86 | 
            -
                  sm. | 
| 87 | 
            -
                  sm.all_items.should include("foo", "bar", "fnord")
         | 
| 78 | 
            +
                  sm.should_receive(:add_to_matrix).with(:anotherinput, 'a', 'foo')
         | 
| 79 | 
            +
                  sm.should_receive(:process_items!).with('foo')
         | 
| 80 | 
            +
                  sm.add_to_matrix!(:anotherinput, 'a', 'foo')
         | 
| 88 81 | 
             
                end
         | 
| 89 82 | 
             
              end
         | 
| 90 83 |  | 
| 91 | 
            -
              describe " | 
| 92 | 
            -
                it " | 
| 84 | 
            +
              describe "related_items" do
         | 
| 85 | 
            +
                it "returns items in the sets across all matrices that the given item is also in" do
         | 
| 93 86 | 
             
                  BaseRecommender.input_matrix(:anotherinput)
         | 
| 94 87 | 
             
                  BaseRecommender.input_matrix(:yetanotherinput)
         | 
| 88 | 
            +
                  BaseRecommender.input_matrix(:finalinput)
         | 
| 95 89 | 
             
                  sm = BaseRecommender.new
         | 
| 96 | 
            -
                  sm.anotherinput. | 
| 97 | 
            -
                  sm.yetanotherinput. | 
| 98 | 
            -
                  sm. | 
| 99 | 
            -
                  sm.yetanotherinput.should_receive(:process!).exactly(1).times
         | 
| 90 | 
            +
                  sm.anotherinput.add_to_set('a', "foo", "bar")
         | 
| 91 | 
            +
                  sm.yetanotherinput.add_to_set('b', "fnord", "shmoo", "bar")
         | 
| 92 | 
            +
                  sm.finalinput.add_to_set('c', "nada")
         | 
| 100 93 | 
             
                  sm.process!
         | 
| 94 | 
            +
                  sm.related_items("bar").should include("foo", "fnord", "shmoo")
         | 
| 95 | 
            +
                  sm.related_items("bar").length.should == 3
         | 
| 101 96 | 
             
                end
         | 
| 102 97 | 
             
              end
         | 
| 103 98 |  | 
| @@ -106,13 +101,13 @@ describe Predictor::Base do | |
| 106 101 | 
             
                  BaseRecommender.input_matrix(:users, weight: 4.0)
         | 
| 107 102 | 
             
                  BaseRecommender.input_matrix(:tags, weight: 1.0)
         | 
| 108 103 | 
             
                  sm = BaseRecommender.new
         | 
| 109 | 
            -
                  sm.users. | 
| 110 | 
            -
                  sm.users. | 
| 111 | 
            -
                  sm.users. | 
| 112 | 
            -
                  sm.users. | 
| 113 | 
            -
                  sm.tags. | 
| 114 | 
            -
                  sm.tags. | 
| 115 | 
            -
                  sm.tags. | 
| 104 | 
            +
                  sm.users.add_to_set('me', "foo", "bar", "fnord")
         | 
| 105 | 
            +
                  sm.users.add_to_set('not_me', "foo", "shmoo")
         | 
| 106 | 
            +
                  sm.users.add_to_set('another', "fnord", "other")
         | 
| 107 | 
            +
                  sm.users.add_to_set('another', "nada")
         | 
| 108 | 
            +
                  sm.tags.add_to_set('tag1', "foo", "fnord", "shmoo")
         | 
| 109 | 
            +
                  sm.tags.add_to_set('tag2', "bar", "shmoo")
         | 
| 110 | 
            +
                  sm.tags.add_to_set('tag3', "shmoo", "nada")
         | 
| 116 111 | 
             
                  sm.process!
         | 
| 117 112 | 
             
                  predictions = sm.predictions_for('me', matrix_label: :users)
         | 
| 118 113 | 
             
                  predictions.should == ["shmoo", "other", "nada"]
         | 
| @@ -123,39 +118,9 @@ describe Predictor::Base do | |
| 123 118 | 
             
                  predictions = sm.predictions_for('me', matrix_label: :users, offset: 1)
         | 
| 124 119 | 
             
                  predictions.should == ["other", "nada"]
         | 
| 125 120 | 
             
                end
         | 
| 126 | 
            -
             | 
| 127 | 
            -
                it "correctly normalizes predictions" do
         | 
| 128 | 
            -
                  BaseRecommender.input_matrix(:users, weight: 1.0)
         | 
| 129 | 
            -
                  BaseRecommender.input_matrix(:tags, weight: 2.0)
         | 
| 130 | 
            -
                  BaseRecommender.input_matrix(:topics, weight: 4.0)
         | 
| 131 | 
            -
             | 
| 132 | 
            -
                  sm = BaseRecommender.new
         | 
| 133 | 
            -
             | 
| 134 | 
            -
                  sm.users.add_set('user1', ["c1", "c2", "c4"])
         | 
| 135 | 
            -
                  sm.users.add_set('user2', ["c3", "c4"])
         | 
| 136 | 
            -
                  sm.topics.add_set('topic1', ["c1", "c4"])
         | 
| 137 | 
            -
                  sm.topics.add_set('topic2', ["c2", "c3"])
         | 
| 138 | 
            -
                  sm.tags.add_set('tag1', ["c1", "c2", "c4"])
         | 
| 139 | 
            -
                  sm.tags.add_set('tag2', ["c1", "c4"])
         | 
| 140 | 
            -
             | 
| 141 | 
            -
                  sm.process!
         | 
| 142 | 
            -
             | 
| 143 | 
            -
                  predictions = sm.predictions_for('user1', matrix_label: :users, with_scores: true, normalize: false)
         | 
| 144 | 
            -
                  predictions.should eq([["c3", 4.5]])
         | 
| 145 | 
            -
                  predictions = sm.predictions_for('user2',  matrix_label: :users, with_scores: true, normalize: false)
         | 
| 146 | 
            -
                  predictions.should eq([["c1", 6.5], ["c2", 5.5]])
         | 
| 147 | 
            -
                  predictions = sm.predictions_for('user1', matrix_label: :users, with_scores: true, normalize: true)
         | 
| 148 | 
            -
                  predictions[0][0].should eq("c3")
         | 
| 149 | 
            -
                  predictions[0][1].should be_within(0.001).of(0.592)
         | 
| 150 | 
            -
                  predictions = sm.predictions_for('user2', matrix_label: :users, with_scores: true, normalize: true)
         | 
| 151 | 
            -
                  predictions[0][0].should eq("c2")
         | 
| 152 | 
            -
                  predictions[0][1].should be_within(0.001).of(1.065)
         | 
| 153 | 
            -
                  predictions[1][0].should eq("c1")
         | 
| 154 | 
            -
                  predictions[1][1].should be_within(0.001).of(0.764)
         | 
| 155 | 
            -
                end
         | 
| 156 121 | 
             
              end
         | 
| 157 122 |  | 
| 158 | 
            -
              describe "similarities_for | 
| 123 | 
            +
              describe "similarities_for" do
         | 
| 159 124 | 
             
                it "should not throw exception for non existing items" do
         | 
| 160 125 | 
             
                  sm = BaseRecommender.new
         | 
| 161 126 | 
             
                  sm.similarities_for("not_existing_item").length.should == 0
         | 
| @@ -168,12 +133,12 @@ describe Predictor::Base do | |
| 168 133 |  | 
| 169 134 | 
             
                  sm = BaseRecommender.new
         | 
| 170 135 |  | 
| 171 | 
            -
                  sm.users. | 
| 172 | 
            -
                  sm.users. | 
| 173 | 
            -
                  sm.topics. | 
| 174 | 
            -
                  sm.topics. | 
| 175 | 
            -
                  sm.tags. | 
| 176 | 
            -
                  sm.tags. | 
| 136 | 
            +
                  sm.users.add_to_set('user1', "c1", "c2", "c4")
         | 
| 137 | 
            +
                  sm.users.add_to_set('user2', "c3", "c4")
         | 
| 138 | 
            +
                  sm.topics.add_to_set('topic1', "c1", "c4")
         | 
| 139 | 
            +
                  sm.topics.add_to_set('topic2', "c2", "c3")
         | 
| 140 | 
            +
                  sm.tags.add_to_set('tag1', "c1", "c2", "c4")
         | 
| 141 | 
            +
                  sm.tags.add_to_set('tag2', "c1", "c4")
         | 
| 177 142 |  | 
| 178 143 | 
             
                  sm.process!
         | 
| 179 144 | 
             
                  sm.similarities_for("c1", with_scores: true).should eq([["c4", 6.5], ["c2", 2.0]])
         | 
| @@ -188,24 +153,125 @@ describe Predictor::Base do | |
| 188 153 | 
             
                  BaseRecommender.input_matrix(:set1)
         | 
| 189 154 | 
             
                  BaseRecommender.input_matrix(:set2)
         | 
| 190 155 | 
             
                  sm = BaseRecommender.new
         | 
| 191 | 
            -
                  sm.set1. | 
| 192 | 
            -
                  sm.set1. | 
| 193 | 
            -
                  sm.set2. | 
| 156 | 
            +
                  sm.set1.add_to_set "item1", "foo", "bar"
         | 
| 157 | 
            +
                  sm.set1.add_to_set "item2", "nada", "bar"
         | 
| 158 | 
            +
                  sm.set2.add_to_set "item3", "bar", "other"
         | 
| 194 159 | 
             
                  sm.sets_for("bar").length.should == 3
         | 
| 195 160 | 
             
                  sm.sets_for("bar").should include("item1", "item2", "item3")
         | 
| 196 161 | 
             
                  sm.sets_for("other").should == ["item3"]
         | 
| 197 162 | 
             
                end
         | 
| 198 163 | 
             
              end
         | 
| 199 164 |  | 
| 165 | 
            +
              describe "process_items!" do
         | 
| 166 | 
            +
                context "with no similarity_limit" do
         | 
| 167 | 
            +
                  it "calculates the similarity between the item and all related_items (other items in a set the given item is in)" do
         | 
| 168 | 
            +
                    BaseRecommender.input_matrix(:myfirstinput)
         | 
| 169 | 
            +
                    BaseRecommender.input_matrix(:mysecondinput)
         | 
| 170 | 
            +
                    BaseRecommender.input_matrix(:mythirdinput, weight: 3.0)
         | 
| 171 | 
            +
                    sm = BaseRecommender.new
         | 
| 172 | 
            +
                    sm.myfirstinput.add_to_set 'set1', 'item1', 'item2'
         | 
| 173 | 
            +
                    sm.mysecondinput.add_to_set 'set2', 'item2', 'item3'
         | 
| 174 | 
            +
                    sm.mythirdinput.add_to_set 'set3', 'item2', 'item3'
         | 
| 175 | 
            +
                    sm.mythirdinput.add_to_set 'set4', 'item1', 'item2', 'item3'
         | 
| 176 | 
            +
                    sm.similarities_for('item2').should be_empty
         | 
| 177 | 
            +
                    sm.process_items!('item2')
         | 
| 178 | 
            +
                    similarities = sm.similarities_for('item2', with_scores: true)
         | 
| 179 | 
            +
                    similarities.should include(["item3", 4.0], ["item1", 2.5])
         | 
| 180 | 
            +
                  end
         | 
| 181 | 
            +
                end
         | 
| 182 | 
            +
             | 
| 183 | 
            +
                context "with a similarity_limit" do
         | 
| 184 | 
            +
                  it "calculates the similarity between the item and all related_items (other items in a set the given item is in), but obeys the similarity_limit" do
         | 
| 185 | 
            +
                    BaseRecommender.input_matrix(:myfirstinput)
         | 
| 186 | 
            +
                    BaseRecommender.input_matrix(:mysecondinput)
         | 
| 187 | 
            +
                    BaseRecommender.input_matrix(:mythirdinput, weight: 3.0)
         | 
| 188 | 
            +
                    BaseRecommender.limit_similarities_to(1)
         | 
| 189 | 
            +
                    sm = BaseRecommender.new
         | 
| 190 | 
            +
                    sm.myfirstinput.add_to_set 'set1', 'item1', 'item2'
         | 
| 191 | 
            +
                    sm.mysecondinput.add_to_set 'set2', 'item2', 'item3'
         | 
| 192 | 
            +
                    sm.mythirdinput.add_to_set 'set3', 'item2', 'item3'
         | 
| 193 | 
            +
                    sm.mythirdinput.add_to_set 'set4', 'item1', 'item2', 'item3'
         | 
| 194 | 
            +
                    sm.similarities_for('item2').should be_empty
         | 
| 195 | 
            +
                    sm.process_items!('item2')
         | 
| 196 | 
            +
                    similarities = sm.similarities_for('item2', with_scores: true)
         | 
| 197 | 
            +
                    similarities.should include(["item3", 4.0])
         | 
| 198 | 
            +
                    similarities.length.should == 1
         | 
| 199 | 
            +
                  end
         | 
| 200 | 
            +
                end
         | 
| 201 | 
            +
              end
         | 
| 202 | 
            +
             | 
| 203 | 
            +
              describe "process!" do
         | 
| 204 | 
            +
                it "should call process_items for all_items's" do
         | 
| 205 | 
            +
                  BaseRecommender.input_matrix(:anotherinput)
         | 
| 206 | 
            +
                  BaseRecommender.input_matrix(:yetanotherinput)
         | 
| 207 | 
            +
                  sm = BaseRecommender.new
         | 
| 208 | 
            +
                  sm.anotherinput.add_to_set('a', "foo", "bar")
         | 
| 209 | 
            +
                  sm.yetanotherinput.add_to_set('b', "fnord", "shmoo")
         | 
| 210 | 
            +
                  sm.all_items.should include("foo", "bar", "fnord", "shmoo")
         | 
| 211 | 
            +
                  sm.should_receive(:process_items!).with(*sm.all_items)
         | 
| 212 | 
            +
                  sm.process!
         | 
| 213 | 
            +
                end
         | 
| 214 | 
            +
              end
         | 
| 215 | 
            +
             | 
| 216 | 
            +
              describe "delete_from_matrix!" do
         | 
| 217 | 
            +
                it "calls delete_item on the matrix" do
         | 
| 218 | 
            +
                  BaseRecommender.input_matrix(:anotherinput)
         | 
| 219 | 
            +
                  BaseRecommender.input_matrix(:yetanotherinput)
         | 
| 220 | 
            +
                  sm = BaseRecommender.new
         | 
| 221 | 
            +
                  sm.anotherinput.add_to_set('a', "foo", "bar")
         | 
| 222 | 
            +
                  sm.yetanotherinput.add_to_set('b', "bar", "shmoo")
         | 
| 223 | 
            +
                  sm.process!
         | 
| 224 | 
            +
                  sm.similarities_for('bar').should include('foo', 'shmoo')
         | 
| 225 | 
            +
                  sm.anotherinput.should_receive(:delete_item).with('foo')
         | 
| 226 | 
            +
                  sm.delete_from_matrix!(:anotherinput, 'foo')
         | 
| 227 | 
            +
                end
         | 
| 228 | 
            +
             | 
| 229 | 
            +
                it "updates similarities" do
         | 
| 230 | 
            +
                  BaseRecommender.input_matrix(:anotherinput)
         | 
| 231 | 
            +
                  BaseRecommender.input_matrix(:yetanotherinput)
         | 
| 232 | 
            +
                  sm = BaseRecommender.new
         | 
| 233 | 
            +
                  sm.anotherinput.add_to_set('a', "foo", "bar")
         | 
| 234 | 
            +
                  sm.yetanotherinput.add_to_set('b', "bar", "shmoo")
         | 
| 235 | 
            +
                  sm.process!
         | 
| 236 | 
            +
                  sm.similarities_for('bar').should include('foo', 'shmoo')
         | 
| 237 | 
            +
                  sm.delete_from_matrix!(:anotherinput, 'foo')
         | 
| 238 | 
            +
                  sm.similarities_for('bar').should == ['shmoo']
         | 
| 239 | 
            +
                end
         | 
| 240 | 
            +
              end
         | 
| 241 | 
            +
             | 
| 200 242 | 
             
              describe "delete_item!" do
         | 
| 201 243 | 
             
                it "should call delete_item on each input_matrix" do
         | 
| 202 244 | 
             
                  BaseRecommender.input_matrix(:myfirstinput)
         | 
| 203 245 | 
             
                  BaseRecommender.input_matrix(:mysecondinput)
         | 
| 204 246 | 
             
                  sm = BaseRecommender.new
         | 
| 205 | 
            -
                  sm.myfirstinput.should_receive(:delete_item | 
| 206 | 
            -
                  sm.mysecondinput.should_receive(:delete_item | 
| 247 | 
            +
                  sm.myfirstinput.should_receive(:delete_item).with("fnorditem")
         | 
| 248 | 
            +
                  sm.mysecondinput.should_receive(:delete_item).with("fnorditem")
         | 
| 207 249 | 
             
                  sm.delete_item!("fnorditem")
         | 
| 208 250 | 
             
                end
         | 
| 251 | 
            +
             | 
| 252 | 
            +
                it "should remove the item from all_items" do
         | 
| 253 | 
            +
                  BaseRecommender.input_matrix(:anotherinput)
         | 
| 254 | 
            +
                  sm = BaseRecommender.new
         | 
| 255 | 
            +
                  sm.anotherinput.add_to_set('a', "foo", "bar")
         | 
| 256 | 
            +
                  sm.process!
         | 
| 257 | 
            +
                  sm.all_items.should include('foo')
         | 
| 258 | 
            +
                  sm.delete_item!('foo')
         | 
| 259 | 
            +
                  sm.all_items.should_not include('foo')
         | 
| 260 | 
            +
                end
         | 
| 261 | 
            +
             | 
| 262 | 
            +
                it "should remove the item's similarities and also remove the item from related_items' similarities" do
         | 
| 263 | 
            +
                  BaseRecommender.input_matrix(:anotherinput)
         | 
| 264 | 
            +
                  BaseRecommender.input_matrix(:yetanotherinput)
         | 
| 265 | 
            +
                  sm = BaseRecommender.new
         | 
| 266 | 
            +
                  sm.anotherinput.add_to_set('a', "foo", "bar")
         | 
| 267 | 
            +
                  sm.yetanotherinput.add_to_set('b', "bar", "shmoo")
         | 
| 268 | 
            +
                  sm.process!
         | 
| 269 | 
            +
                  sm.similarities_for('bar').should include('foo', 'shmoo')
         | 
| 270 | 
            +
                  sm.similarities_for('shmoo').should include('bar')
         | 
| 271 | 
            +
                  sm.delete_item!('shmoo')
         | 
| 272 | 
            +
                  sm.similarities_for('bar').should_not include('shmoo')
         | 
| 273 | 
            +
                  sm.similarities_for('shmoo').should be_empty
         | 
| 274 | 
            +
                end
         | 
| 209 275 | 
             
              end
         | 
| 210 276 |  | 
| 211 277 | 
             
              describe "clean!" do
         | 
| @@ -213,9 +279,9 @@ describe Predictor::Base do | |
| 213 279 | 
             
                  BaseRecommender.input_matrix(:set1)
         | 
| 214 280 | 
             
                  BaseRecommender.input_matrix(:set2)
         | 
| 215 281 | 
             
                  sm = BaseRecommender.new
         | 
| 216 | 
            -
                  sm.set1. | 
| 217 | 
            -
                  sm.set1. | 
| 218 | 
            -
                  sm.set2. | 
| 282 | 
            +
                  sm.set1.add_to_set "item1", "foo", "bar"
         | 
| 283 | 
            +
                  sm.set1.add_to_set "item2", "nada", "bar"
         | 
| 284 | 
            +
                  sm.set2.add_to_set "item3", "bar", "other"
         | 
| 219 285 | 
             
                  Predictor.redis.keys("#{sm.redis_prefix}:*").should_not be_empty
         | 
| 220 286 | 
             
                  sm.clean!
         | 
| 221 287 | 
             
                  Predictor.redis.keys("#{sm.redis_prefix}:*").should be_empty
         |