recommendify-ruby 0.3.8

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,89 @@
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" do
23
+ @matrix.ccmatrix["bar", "fnord"] = 2
24
+ @matrix.add_single("user123", "fnord", ["foo", "bar"])
25
+ @matrix.ccmatrix["bar", "fnord"].should == 3
26
+ @matrix.ccmatrix["foo", "fnord"].should == 1
27
+ end
28
+
29
+ it "should increment the item count on single item addition" do
30
+ @matrix.send(:item_count_incr, "fnordfnord")
31
+ @matrix.send(:item_count_incr, "fnordfnord")
32
+ @matrix.send(:item_count_incr, "foofnord")
33
+ @matrix.add_single("user123", "fnordfnord", ["foofnord", "barfnord"])
34
+ @matrix.send(:item_count, "foofnord").should == 1
35
+ @matrix.send(:item_count, "barfnord").should == 0
36
+ @matrix.send(:item_count, "fnordfnord").should == 3
37
+ end
38
+
39
+ it "should calculate all item<->item pairs (3)" do
40
+ res = @matrix.send(:all_pairs, ["foo", "bar", "fnord"])
41
+ res.length.should == 3
42
+ res.should include("bar:foo")
43
+ res.should include("fnord:foo")
44
+ res.should include("bar:fnord")
45
+ end
46
+
47
+ it "should calculate all item<->item pairs (6)" do
48
+ res = @matrix.send(:all_pairs, ["foo", "bar", "fnord", "blubb"])
49
+ res.length.should == 6
50
+ res.should include("bar:foo")
51
+ res.should include("fnord:foo")
52
+ res.should include("bar:fnord")
53
+ res.should include("blubb:foo")
54
+ res.should include("blubb:fnord")
55
+ res.should include("bar:blubb")
56
+ end
57
+
58
+ it "should return all_items" do
59
+ @matrix.add_set("user42", ["fnord", "blubb"])
60
+ @matrix.add_set("user23", ["hans", "wurst"])
61
+ @matrix.all_items.length.should == 4
62
+ @matrix.all_items.should include("wurst")
63
+ @matrix.all_items.should include("fnord")
64
+ @matrix.all_items.should include("hans")
65
+ @matrix.all_items.should include("wurst")
66
+ end
67
+
68
+ it "should delete all item<->item pairs on item deletion" do
69
+ @matrix.ccmatrix["foo", "fnord"] = 2
70
+ @matrix.add_set("user123", ["foo", "bar", "fnord"])
71
+ @matrix.add_set("user456", ["fnord", "blubb"])
72
+ @matrix.ccmatrix["bar", "foo"].should == 1
73
+ @matrix.ccmatrix["foo", "fnord"].should == 3
74
+ @matrix.ccmatrix["blubb", "fnord"].should == 1
75
+ @matrix.delete_item("fnord")
76
+ @matrix.ccmatrix["bar", "foo"].should == 1
77
+ @matrix.ccmatrix["foo", "fnord"].should == 0
78
+ @matrix.ccmatrix["blubb", "fnord"].should == 0
79
+ end
80
+
81
+ it "should delete the item count on deletion" do
82
+ @matrix.add_set("user123", ["foo", "bar", "fnord"])
83
+ @matrix.add_set("user456", ["fnord", "blubb"])
84
+ @matrix.send(:item_count, "fnord").should == 2
85
+ @matrix.delete_item("fnord")
86
+ @matrix.send(:item_count, "fnord").should == 0
87
+ end
88
+
89
+ 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,95 @@
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
+ it "should call run_native when the native option was passed" do
59
+ Recommendify::JaccardInputMatrix.class_eval do
60
+ def check_native; true; end
61
+ end
62
+ matrix = Recommendify::JaccardInputMatrix.new(
63
+ :redis_prefix => "recommendify-test",
64
+ :native => true,
65
+ :key => "mymatrix"
66
+ )
67
+ matrix.should_receive(:run_native).with("fnord").and_return(true)
68
+ matrix.similarities_for("fnord")
69
+ end
70
+
71
+ it "should return the correct redis url" do
72
+ @matrix.send(:redis_url).should == "127.0.0.1:6379"
73
+ end
74
+
75
+ private
76
+
77
+ def add_two_item_test_data!(matrix)
78
+ matrix.add_set("user42", ["fnord", "blubb"])
79
+ matrix.add_set("user44", ["blubb"])
80
+ matrix.add_set("user46", ["fnord"])
81
+ matrix.add_set("user48", ["fnord", "blubb"])
82
+ matrix.add_set("user50", ["fnord"])
83
+ end
84
+
85
+ def add_three_item_test_data!(matrix)
86
+ matrix.add_set("user42", ["fnord", "blubb", "shmoo"])
87
+ matrix.add_set("user44", ["blubb"])
88
+ matrix.add_set("user46", ["fnord", "shmoo"])
89
+ matrix.add_set("user48", ["fnord", "blubb"])
90
+ matrix.add_set("user50", ["fnord", "shmoo"])
91
+ end
92
+
93
+ end
94
+
95
+
@@ -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