recommendify 0.0.1

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.
@@ -0,0 +1,54 @@
1
+ share_examples_for Recommendify::CCMatrix do
2
+
3
+ it "should build a sparsematrix with the correct key" do
4
+ @matrix.ccmatrix.redis_key.should == "recommendify-test:mymatrix:ccmatrix"
5
+ end
6
+
7
+ it "should increment all item counts on set addition" do
8
+ Recommendify.redis.hset("recommendify-test:mymatrix:items", "bar", 2)
9
+ @matrix.add_set("user123", ["foo", "bar"])
10
+ Recommendify.redis.hget("recommendify-test:mymatrix:items", "bar").to_i.should == 3
11
+ Recommendify.redis.hget("recommendify-test:mymatrix:items", "foo").to_i.should == 1
12
+ end
13
+
14
+ it "should increment all item<->item pairs on set addition" do
15
+ @matrix.ccmatrix["bar", "foo"] = 2
16
+ res = @matrix.add_set("user123", ["foo", "bar", "fnord"])
17
+ res.length.should == 3
18
+ @matrix.ccmatrix["bar", "foo"].should == 3
19
+ @matrix.ccmatrix["foo", "fnord"].should == 1
20
+ end
21
+
22
+ it "should increment all item<->item paris on single item addition"
23
+ it "should increment the item count on single item addition"
24
+
25
+ it "should calculate all item<->item pairs (3)" do
26
+ res = @matrix.send(:all_pairs, ["foo", "bar", "fnord"])
27
+ res.length.should == 3
28
+ res.should include("bar:foo")
29
+ res.should include("fnord:foo")
30
+ res.should include("bar:fnord")
31
+ end
32
+
33
+ it "should calculate all item<->item pairs (6)" do
34
+ res = @matrix.send(:all_pairs, ["foo", "bar", "fnord", "blubb"])
35
+ res.length.should == 6
36
+ res.should include("bar:foo")
37
+ res.should include("fnord:foo")
38
+ res.should include("bar:fnord")
39
+ res.should include("blubb:foo")
40
+ res.should include("blubb:fnord")
41
+ res.should include("bar:blubb")
42
+ end
43
+
44
+ it "should return all_items" do
45
+ @matrix.add_set("user42", ["fnord", "blubb"])
46
+ @matrix.add_set("user23", ["hans", "wurst"])
47
+ @matrix.all_items.length.should == 4
48
+ @matrix.all_items.should include("wurst")
49
+ @matrix.all_items.should include("fnord")
50
+ @matrix.all_items.should include("hans")
51
+ @matrix.all_items.should include("wurst")
52
+ end
53
+
54
+ end
@@ -0,0 +1,18 @@
1
+ require ::File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe Recommendify::CosineInputMatrix do
4
+
5
+ before(:all) do
6
+ @matrix = Recommendify::CosineInputMatrix.new(:redis_prefix => "recommendify-test", :key => "mymatrix")
7
+ end
8
+
9
+ before(:each) do
10
+ flush_redis!
11
+ end
12
+
13
+ it_should_behave_like Recommendify::InputMatrix
14
+ it_should_behave_like Recommendify::CCMatrix
15
+
16
+ it "should calculate the correct cosine similarity (here be dragons)"
17
+
18
+ end
@@ -0,0 +1,27 @@
1
+ share_examples_for Recommendify::InputMatrix do
2
+
3
+ it "should build the correct keys" do
4
+ @matrix.redis_key.should == "recommendify-test:mymatrix"
5
+ end
6
+
7
+ it "should respond to add_set" do
8
+ @matrix.respond_to?(:add_set).should == true
9
+ end
10
+
11
+ it "should respond to add_single" do
12
+ @matrix.respond_to?(:add_single).should == true
13
+ end
14
+
15
+ it "should respond to similarity" do
16
+ @matrix.respond_to?(:similarity).should == true
17
+ end
18
+
19
+ it "should respond to similarities_for" do
20
+ @matrix.respond_to?(:similarities_for).should == true
21
+ end
22
+
23
+ it "should respond to all_items" do
24
+ @matrix.respond_to?(:all_items).should == true
25
+ end
26
+
27
+ end
@@ -0,0 +1,29 @@
1
+ require ::File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe Recommendify::InputMatrix do
4
+
5
+ before(:all) do
6
+ @matrix = Recommendify::InputMatrix.new(:redis_prefix => "recommendify-test", :key => "mymatrix")
7
+ end
8
+
9
+ before(:each) do
10
+ flush_redis!
11
+ end
12
+
13
+ it_should_behave_like Recommendify::InputMatrix
14
+
15
+ describe "object creation" do
16
+
17
+ it "should create an object with the correct class" do
18
+ obj = Recommendify::InputMatrix.create(:key => "fubar", :similarity_func => :jaccard)
19
+ obj.should be_a(Recommendify::JaccardInputMatrix)
20
+ end
21
+
22
+ it "should create an object with the correct class and pass opts" do
23
+ obj = Recommendify::InputMatrix.create(:key => "fubar", :similarity_func => :jaccard)
24
+ obj.instance_variable_get(:@opts)[:key].should == "fubar"
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,78 @@
1
+ require ::File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe Recommendify::JaccardInputMatrix do
4
+
5
+ before(:all) do
6
+ @matrix = Recommendify::JaccardInputMatrix.new(:redis_prefix => "recommendify-test", :key => "mymatrix")
7
+ end
8
+
9
+ before(:each) do
10
+ flush_redis!
11
+ end
12
+
13
+ it_should_behave_like Recommendify::InputMatrix
14
+ it_should_behave_like Recommendify::CCMatrix
15
+
16
+ it "should calculate the correct jaccard index" do
17
+ @matrix.send(:calculate_jaccard,
18
+ ["foo", "bar", "fnord", "blubb"],
19
+ ["bar", "fnord", "shmoo", "snafu"]
20
+ ).should == 2.0/6.0
21
+ end
22
+
23
+ it "should calculate the correct similarity between to items" do
24
+ add_two_item_test_data!(@matrix)
25
+ # sim(fnord,blubb) = (users(fnord) & users(blub)) / (users(fnord) + users(blubb))
26
+ # => {user42 user48} / {user42 user46 user48 user50} + {user42 user44 user48}
27
+ # => {user42 user48} / {user42 user44 user46 user48 user50}
28
+ # => 2 / 5 => 0.4
29
+ @matrix.similarity("fnord", "blubb").should == 0.4
30
+ @matrix.similarity("blubb", "fnord").should == 0.4
31
+ end
32
+
33
+ it "should calculate all similarities for an item (1/3)" do
34
+ add_three_item_test_data!(@matrix)
35
+ res = @matrix.similarities_for("fnord")
36
+ res.length.should == 2
37
+ res.should include ["shmoo", 0.75]
38
+ res.should include ["blubb", 0.4]
39
+ end
40
+
41
+ it "should calculate all similarities for an item (2/3)" do
42
+ add_three_item_test_data!(@matrix)
43
+ res = @matrix.similarities_for("shmoo")
44
+ res.length.should == 2
45
+ res.should include ["blubb", 0.2]
46
+ res.should include ["fnord", 0.75]
47
+ end
48
+
49
+
50
+ it "should calculate all similarities for an item (3/3)" do
51
+ add_three_item_test_data!(@matrix)
52
+ res = @matrix.similarities_for("blubb")
53
+ res.length.should == 2
54
+ res.should include ["shmoo", 0.2]
55
+ res.should include ["fnord", 0.4]
56
+ end
57
+
58
+ private
59
+
60
+ def add_two_item_test_data!(matrix)
61
+ matrix.add_set("user42", ["fnord", "blubb"])
62
+ matrix.add_set("user44", ["blubb"])
63
+ matrix.add_set("user46", ["fnord"])
64
+ matrix.add_set("user48", ["fnord", "blubb"])
65
+ matrix.add_set("user50", ["fnord"])
66
+ end
67
+
68
+ def add_three_item_test_data!(matrix)
69
+ matrix.add_set("user42", ["fnord", "blubb", "shmoo"])
70
+ matrix.add_set("user44", ["blubb"])
71
+ matrix.add_set("user46", ["fnord", "shmoo"])
72
+ matrix.add_set("user48", ["fnord", "blubb"])
73
+ matrix.add_set("user50", ["fnord", "shmoo"])
74
+ end
75
+
76
+ end
77
+
78
+
@@ -0,0 +1,28 @@
1
+ require ::File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe Recommendify do
4
+
5
+ it "should store a redis connection" do
6
+ Recommendify.redis = "asd"
7
+ Recommendify.redis.should == "asd"
8
+ end
9
+
10
+ it "should raise an exception if unconfigured redis connection is accessed" do
11
+ Recommendify.redis = nil
12
+ lambda{ ecommendify.redis }.should raise_error
13
+ end
14
+
15
+ it "should capitalize a string" do
16
+ Recommendify.capitalize("fuubar").should == "Fuubar"
17
+ Recommendify.capitalize("fuUBar").should == "Fuubar"
18
+ Recommendify.capitalize("FUUBAR").should == "Fuubar"
19
+ Recommendify.capitalize("Fuubar").should == "Fuubar"
20
+ end
21
+
22
+ it "should constantize a string" do
23
+ obj = Recommendify.constantize("JaccardInputMatrix")
24
+ # should_be doesn't work here...
25
+ obj.inspect.should == "Recommendify::JaccardInputMatrix"
26
+ end
27
+
28
+ end
@@ -0,0 +1,93 @@
1
+ require ::File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe Recommendify::SimilarityMatrix do
4
+
5
+ before(:each) do
6
+ flush_redis!
7
+ @matrix = Recommendify::SimilarityMatrix.new(:redis_prefix => "recommendify-test", :key => "mymatrix")
8
+ end
9
+
10
+ describe "sorted sets per item" do
11
+
12
+ it "should store member similarities" do
13
+ @matrix.update("item_foo", [["item_bar", 0.7], ["item_fnord", 0.3]])
14
+ @matrix.write_queue["item_foo"].length.should == 2
15
+ end
16
+
17
+ it "should store member similarities uniquely" do
18
+ @matrix.update("item_fnord", [["item_bar", 0.7], ["item_bar", 0.3]])
19
+ @matrix.write_queue["item_fnord"].length.should == 1
20
+ end
21
+
22
+ it "should store member similarities uniquely and sum the scores" do
23
+ @matrix.update("item_fnord", [["item_bar", 0.7], ["item_bar", 0.3]])
24
+ @matrix.write_queue["item_fnord"].should == {"item_bar" => 1.0}
25
+ end
26
+
27
+ it "should store multiple member similarities uniquely and sum the scores" do
28
+ @matrix.update("item_fnord", [["item_bar", 0.7], ["item_bar", 0.3], ["item_foo", 0.75]])
29
+ @matrix.write_queue["item_fnord"].should == {"item_bar" => 1.0, "item_foo" => 0.75}
30
+ end
31
+
32
+ it "should store multiple member similarities uniquely and sum the scores on multiple updates" do
33
+ @matrix.update("item_fnord", [["item_bar", 0.7], ["item_foo", 0.75]])
34
+ @matrix.update("item_fnord", [["item_fnord", 0.3], ["item_bar", 0.3], ["item_foo", 0.75]])
35
+ @matrix.write_queue["item_fnord"].should == {"item_bar" => 1.0, "item_foo" => 1.5, "item_fnord" => 0.3}
36
+ end
37
+
38
+ it "should retrieve the members" do
39
+ @matrix.update("item_fnord", [["item_bar", 0.7], ["item_bar", 0.3], ["item_foo", 0.75]])
40
+ @matrix["item_fnord"].should == {"item_bar" => 1.0, "item_foo" => 0.75}
41
+ end
42
+
43
+ end
44
+
45
+ describe "serialization/loading to/from redis" do
46
+
47
+ it "should serialize a member of the write_queue correctly" do
48
+ @matrix.update("item_fnord", [["item_fnord", 0.3], ["item_bar", 0.3], ["item_foo", 0.75]])
49
+ @matrix.update("item_fnord", [["item_bar", 0.7], ["item_bar", 0.3]])
50
+ @matrix.send(:serialize_item, "item_fnord").should == "item_bar:1.3|item_foo:0.75|item_fnord:0.3"
51
+ end
52
+
53
+ it "should write the n-most similar members and scores to redis on commit_item!" do
54
+ @matrix.update("item_fnord", [["item_fnord", 0.3], ["item_bar", 0.3], ["item_foo", 0.75]])
55
+ @matrix.update("item_fnord", [["item_bar", 0.7], ["item_bar", 0.3]])
56
+ @matrix.commit_item!("item_fnord")
57
+ Recommendify.redis.hget("recommendify-test:mymatrix", "item_fnord").should == "item_bar:1.3|item_foo:0.75|item_fnord:0.3"
58
+ end
59
+
60
+ it "should not write more than max_neighbors scores to redis on commit_item!" do
61
+ 60.times{ |n| @matrix.update("item_fnord", [["item_#{n}", n.to_f/100.0]]) }
62
+ @matrix.commit_item!("item_fnord")
63
+ Recommendify.redis.hget("recommendify-test:mymatrix", "item_fnord").split("|").length.should == 50
64
+ end
65
+
66
+ it "should not write more neighbors with zero-scores to redis on commit_item!" do
67
+ @matrix.update("item_fnord", [["item_fnord", 0.0], ["item_bar", 0.3], ["item_foo", 0.75]])
68
+ @matrix.update("item_fnord", [["item_bar", 0.7], ["item_bar", 0.3]])
69
+ @matrix.commit_item!("item_fnord")
70
+ Recommendify.redis.hget("recommendify-test:mymatrix", "item_fnord").split("|").length.should == 2
71
+ end
72
+
73
+ it "should clear the item from the write_queue after commit_item!" do
74
+ 60.times{ |n| @matrix.update("item_fnord", [["item_#{n}", n.to_f/100.0]]) }
75
+ @matrix.write_queue["item_fnord"].length.should == 60
76
+ @matrix.commit_item!("item_fnord")
77
+ @matrix.write_queue["item_fnord"].length.should == 0
78
+ end
79
+
80
+ it "should retrieve the n-most similar neighbors after stored" do
81
+ Recommendify.redis.hset("recommendify-test:mymatrix", "item_fnord", "item_blubb:0.6|item_foo:0.4")
82
+ @matrix["item_fnord"].keys.should include("item_blubb")
83
+ @matrix["item_fnord"].keys.should include("item_foo")
84
+ end
85
+
86
+ it "should retrieve the n-most similar neighbors after stored with scores" do
87
+ Recommendify.redis.hset("recommendify-test:mymatrix", "item_fnord", "item_blubb:0.6|item_foo:0.4")
88
+ @matrix["item_fnord"].should == {"item_blubb" => 0.6, "item_foo" => 0.4}
89
+ end
90
+
91
+ end
92
+
93
+ end
@@ -0,0 +1,78 @@
1
+ require ::File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe Recommendify::SparseMatrix do
4
+
5
+ before(:all) do
6
+ @sm = Recommendify::SparseMatrix.new(:redis_prefix => "recommendify-test", :key => "mysparsematrix")
7
+ end
8
+
9
+ before(:each) do
10
+ flush_redis!
11
+ end
12
+
13
+ it "should build the correct keys" do
14
+ @sm.redis_key.should == "recommendify-test:mysparsematrix"
15
+ end
16
+
17
+ it "should retrieve a value" do
18
+ Recommendify.redis.hset("recommendify-test:mysparsematrix", "bar:foo", 43)
19
+ @sm["bar", "foo"].to_i.should == 43
20
+ end
21
+
22
+ it "should retrieve a value regardless of parameter order" do
23
+ Recommendify.redis.hset("recommendify-test:mysparsematrix", "one:two", 14)
24
+ @sm["one", "two"].to_i.should == 14
25
+ @sm["two", "one"].to_i.should == 14
26
+ end
27
+
28
+ it "should store a value" do
29
+ @sm["bar", "foo"] = 123
30
+ Recommendify.redis.hget("recommendify-test:mysparsematrix", "bar:foo").to_i.should == 123
31
+ end
32
+
33
+ it "should store a value regardless of parameter order" do
34
+ @sm["foo", "bar"] = 126
35
+ Recommendify.redis.hget("recommendify-test:mysparsematrix", "bar:foo").to_i.should == 126
36
+ end
37
+
38
+ it "should return 0 if the key is not found" do
39
+ @sm["not", "set"].should == 0
40
+ end
41
+
42
+ it "should increment a value" do
43
+ @sm["foo", "bar"] = 1000
44
+ @sm.incr("foo", "bar")
45
+ Recommendify.redis.hget("recommendify-test:mysparsematrix", "bar:foo").to_i.should == 1001
46
+ end
47
+
48
+ it "should increment a value regardless of parameter order" do
49
+ @sm["foo", "bar"] = 2000
50
+ @sm.incr("bar", "foo")
51
+ Recommendify.redis.hget("recommendify-test:mysparsematrix", "bar:foo").to_i.should == 2001
52
+ end
53
+
54
+ it "should not create unneseccary keys" do
55
+ @sm["foo", "bar"] = 90
56
+ @sm["5asd6", "bar"] = 260
57
+ @sm["foo", "bar"] = 45
58
+ @sm["foo", "jefs"] = 26
59
+ Recommendify.redis.hkeys("recommendify-test:mysparsematrix").length.should == 3
60
+ end
61
+
62
+ it "should create a key if the value is not 0" do
63
+ @sm["foo", "jefs"] = 26
64
+ Recommendify.redis.hkeys("recommendify-test:mysparsematrix").length.should == 1
65
+ end
66
+
67
+ it "should not create a key if the value is 0" do
68
+ @sm["foo", "jefs"] = 0
69
+ Recommendify.redis.hkeys("recommendify-test:mysparsematrix").length.should == 0
70
+ end
71
+
72
+ it "should delete a key if the value is set to 0" do
73
+ Recommendify.redis.hset("recommendify-test:mysparsematrix", "bar:foo", 43)
74
+ @sm["bar", "foo"] = 0
75
+ Recommendify.redis.hkeys("recommendify-test:mysparsematrix").length.should == 0
76
+ end
77
+
78
+ end
@@ -0,0 +1,42 @@
1
+ require "rspec"
2
+ require "redis"
3
+
4
+ require ::File.expand_path('../../lib/recommendify', __FILE__)
5
+
6
+ require ::File.expand_path('../input_matrix_shared.rb', __FILE__)
7
+ require ::File.expand_path('../cc_matrix_shared.rb', __FILE__)
8
+
9
+ def flush_redis!
10
+ Recommendify.redis = Redis.new
11
+ Recommendify.redis.keys("recommendify-test*").each do |k|
12
+ Recommendify.redis.del(k)
13
+ end
14
+ end
15
+
16
+ class Recommendify::Base
17
+
18
+ def redis_prefix
19
+ "recommendify-test"
20
+ end
21
+
22
+ end
23
+
24
+
25
+ class TestRecommender < Recommendify::Base
26
+
27
+ input_matrix :jaccard_one,
28
+ :similarity_func => :jaccard
29
+
30
+ end
31
+
32
+ class Recommendify::TestInputMatrix
33
+
34
+ def initialize(opts)
35
+ @opts = opts
36
+ end
37
+
38
+ def method_missing(method, *args)
39
+ @opts[method]
40
+ end
41
+
42
+ end