recommendify 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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