commendo 1.2.4 → 2.0.0
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 +3 -0
- data/bin/commendo-create-mysql-db +3 -0
- data/bin/commendo-create.sql +99 -0
- data/bin/commendo-load-tsv +11 -5
- data/bin/commendo-load-tsv-mysql.rb +43 -0
- data/bin/commendo-time-mysql.rb +31 -0
- data/commendo.gemspec +4 -2
- data/lib/commendo.rb +24 -0
- data/lib/commendo/configuration.rb +25 -0
- data/lib/commendo/content_set.rb +13 -182
- data/lib/commendo/mysql-backed/content_set.rb +152 -0
- data/lib/commendo/mysql-backed/tag_set.rb +81 -0
- data/lib/commendo/mysql-backed/weighted_group.rb +40 -0
- data/lib/commendo/redis-backed/content_set.rb +194 -0
- data/lib/commendo/{pair_comparison.lua → redis-backed/pair_comparison.lua} +0 -0
- data/lib/commendo/{similarity.lua → redis-backed/similarity.lua} +0 -0
- data/lib/commendo/redis-backed/tag_set.rb +54 -0
- data/lib/commendo/redis-backed/weighted_group.rb +54 -0
- data/lib/commendo/tag_set.rb +6 -42
- data/lib/commendo/version.rb +1 -1
- data/lib/commendo/weighted_group.rb +7 -41
- data/lib/mysql2/client.rb +17 -0
- data/model 2.mwb +0 -0
- data/sql_model.mwb +0 -0
- data/test/configuration_test.rb +71 -0
- data/test/mysql_content_set_test.rb +40 -0
- data/test/mysql_tag_set_test.rb +34 -0
- data/test/mysql_weighted_group_test.rb +54 -0
- data/test/redis_content_set_test.rb +57 -0
- data/test/redis_tag_set_test.rb +31 -0
- data/test/redis_weighted_group_test.rb +49 -0
- data/test/tests_for_content_sets.rb +379 -0
- data/test/tests_for_tag_sets.rb +130 -0
- data/test/tests_for_weighted_groups.rb +106 -0
- metadata +72 -12
- data/test/content_set_test.rb +0 -408
- data/test/tag_set_test.rb +0 -128
- data/test/weighted_group_test.rb +0 -191
@@ -0,0 +1,130 @@
|
|
1
|
+
module TestsForTagSets
|
2
|
+
|
3
|
+
def test_adds_tags_for_resource
|
4
|
+
assert_equal [], @ts.get(1)
|
5
|
+
@ts.add(1, 'foo', 'bar', 'baz')
|
6
|
+
expected = @ts.get(1)
|
7
|
+
assert_equal %w(bar baz foo).sort, expected.sort
|
8
|
+
@ts.add(1, 'qux', 'qip')
|
9
|
+
expected = @ts.get(1)
|
10
|
+
assert_equal %w(bar baz foo qip qux).sort, expected.sort
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_isolates_by_key_base
|
14
|
+
ts1 = create_tag_set('TagSetOne')
|
15
|
+
ts2 = create_tag_set('TagSetTwo')
|
16
|
+
assert_equal [], ts1.get(1)
|
17
|
+
assert_equal [], ts2.get(1)
|
18
|
+
|
19
|
+
ts1.add(1, 'foo', 'bar', 'baz')
|
20
|
+
|
21
|
+
assert_equal %w(bar baz foo).sort, ts1.get(1).sort
|
22
|
+
assert_equal [], ts2.get(1)
|
23
|
+
|
24
|
+
ts2.add(1, 'qux', 'qip')
|
25
|
+
assert_equal %w(bar baz foo).sort, ts1.get(1).sort
|
26
|
+
assert_equal %w(qux qip).sort, ts2.get(1).sort
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_empty?
|
30
|
+
assert @ts.respond_to? :empty?
|
31
|
+
assert @ts.empty?
|
32
|
+
@ts.add(1, 'qux', 'qip')
|
33
|
+
refute @ts.empty?
|
34
|
+
@ts.delete(1)
|
35
|
+
assert @ts.empty?
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_sets_tags_for_resource
|
39
|
+
assert_equal [], @ts.get(1)
|
40
|
+
@ts.set(1, 'foo', 'bar', 'baz')
|
41
|
+
assert_equal %w(bar baz foo).sort, @ts.get(1).sort
|
42
|
+
@ts.set(1, 'qux', 'qip')
|
43
|
+
assert_equal %w(qip qux).sort, @ts.get(1).sort
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_sets_tags_when_empty
|
47
|
+
@ts.set(1, 'foo', 'bar', 'baz')
|
48
|
+
@ts.set(2, 'qux', 'qip')
|
49
|
+
assert_equal %w(bar baz foo).sort, @ts.get(1).sort
|
50
|
+
assert_equal %w(qip qux).sort, @ts.get(2).sort
|
51
|
+
@ts.set(1, *[])
|
52
|
+
assert_equal [], @ts.get(1)
|
53
|
+
assert_equal %w(qip qux).sort, @ts.get(2).sort
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_deletes_all_tags_for_resource
|
57
|
+
@ts.set(1, 'foo', 'bar', 'baz')
|
58
|
+
@ts.set(2, 'qux', 'qip')
|
59
|
+
assert_equal %w(bar baz foo).sort, @ts.get(1).sort
|
60
|
+
assert_equal %w(qip qux).sort, @ts.get(2).sort
|
61
|
+
@ts.delete(1)
|
62
|
+
assert_equal [], @ts.get(1)
|
63
|
+
assert_equal %w(qip qux).sort, @ts.get(2).sort
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_deletes_given_tags_for_resource
|
67
|
+
assert_equal [], @ts.get(1)
|
68
|
+
@ts.set(1, 'foo', 'bar', 'baz', 'qux', 'qip')
|
69
|
+
@ts.delete(1, 'qux', 'qip')
|
70
|
+
assert_equal %w(bar baz foo).sort, @ts.get(1).sort
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_matches_tags
|
74
|
+
@ts.set(1, 'foo', 'bar', 'baz')
|
75
|
+
@ts.set(2, 'qux', 'qip')
|
76
|
+
|
77
|
+
assert @ts.matches(1, ['foo'])
|
78
|
+
assert @ts.matches(1, %w(bar baz))
|
79
|
+
assert @ts.matches(1, %w(bar baz foo))
|
80
|
+
refute @ts.matches(1, ['qux'])
|
81
|
+
refute @ts.matches(1, ['qip'])
|
82
|
+
|
83
|
+
refute @ts.matches(2, ['foo'])
|
84
|
+
refute @ts.matches(2, %w(bar baz))
|
85
|
+
refute @ts.matches(2, %w(bar baz foo))
|
86
|
+
assert @ts.matches(2, %w(qux qip))
|
87
|
+
assert @ts.matches(2, ['qux'])
|
88
|
+
assert @ts.matches(2, ['qip'])
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_matches_exclude_tags
|
92
|
+
@ts.set(1, 'foo', 'bar', 'baz')
|
93
|
+
@ts.set(2, 'qux', 'qip')
|
94
|
+
|
95
|
+
refute @ts.matches(1, nil, ['foo'])
|
96
|
+
refute @ts.matches(1, [], ['foo'])
|
97
|
+
refute @ts.matches(1, [], %w(bar baz))
|
98
|
+
refute @ts.matches(1, [], %w(bar baz foo))
|
99
|
+
assert @ts.matches(1, [], ['qux'])
|
100
|
+
assert @ts.matches(1, [], ['qip'])
|
101
|
+
|
102
|
+
assert @ts.matches(2, nil, ['foo'])
|
103
|
+
assert @ts.matches(2, [], ['foo'])
|
104
|
+
assert @ts.matches(2, [], %w(bar baz))
|
105
|
+
assert @ts.matches(2, [], %w(bar baz foo))
|
106
|
+
refute @ts.matches(2, [], %w(qux qip))
|
107
|
+
refute @ts.matches(2, [], ['qux'])
|
108
|
+
refute @ts.matches(2, [], ['qip'])
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_matches_include_and_exclude_tags
|
112
|
+
@ts.set(1, 'foo', 'bar', 'baz')
|
113
|
+
@ts.set(2, 'qux', 'qip')
|
114
|
+
|
115
|
+
refute @ts.matches(1, ['foo'], ['bar'])
|
116
|
+
refute @ts.matches(1, ['bar'], ['foo'])
|
117
|
+
|
118
|
+
assert @ts.matches(1, ['foo'], [])
|
119
|
+
assert @ts.matches(1, ['foo'], nil)
|
120
|
+
assert @ts.matches(1, ['foo'], ['qux'])
|
121
|
+
|
122
|
+
assert @ts.matches(2, ['qip'], ['foo'])
|
123
|
+
assert @ts.matches(2, ['qux'], %w(bar baz))
|
124
|
+
assert @ts.matches(2, ['qip'], %w(bar baz foo))
|
125
|
+
refute @ts.matches(2, ['qip'], %w(qux qip))
|
126
|
+
refute @ts.matches(2, ['qip'], ['qux'])
|
127
|
+
refute @ts.matches(2, ['qux'], ['qip'])
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module TestsForWeightedGroups
|
2
|
+
|
3
|
+
def test_calls_each_content_set
|
4
|
+
expected = [
|
5
|
+
{resource: '6', similarity: 74.037},
|
6
|
+
{resource: '12', similarity: 55.5},
|
7
|
+
{resource: '9', similarity: 6.67},
|
8
|
+
{resource: '3', similarity: 4.0},
|
9
|
+
{resource: '21', similarity: 2.86},
|
10
|
+
{resource: '15', similarity: 2.86}
|
11
|
+
]
|
12
|
+
assert_equal expected, @weighted_group.similar_to(18)
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_calls_each_content_set_with_limits
|
16
|
+
expected = [
|
17
|
+
{resource: '6', similarity: 74.037},
|
18
|
+
{resource: '12', similarity: 55.5},
|
19
|
+
{resource: '9', similarity: 6.67},
|
20
|
+
{resource: '3', similarity: 4.0},
|
21
|
+
{resource: '21', similarity: 2.86},
|
22
|
+
{resource: '15', similarity: 2.86}
|
23
|
+
]
|
24
|
+
assert_equal expected[0..0], @weighted_group.similar_to(18, 1)
|
25
|
+
assert_equal expected[0..2], @weighted_group.similar_to(18, 3)
|
26
|
+
assert_equal expected, @weighted_group.similar_to(18, 6)
|
27
|
+
assert_equal expected, @weighted_group.similar_to(18, 99)
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_filters_include_recommendations
|
31
|
+
expected = [{resource: '15', similarity: 2.86}]
|
32
|
+
@weighted_group.tag_set = @tag_set
|
33
|
+
assert_equal expected, @weighted_group.filtered_similar_to(18, include: ['mod5'])
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_filters_exclude_recommendations
|
37
|
+
expected = [
|
38
|
+
{resource: '6', similarity: 74.037},
|
39
|
+
{resource: '12', similarity: 55.5},
|
40
|
+
{resource: '9', similarity: 6.67},
|
41
|
+
{resource: '3', similarity: 4.0}
|
42
|
+
]
|
43
|
+
@weighted_group.tag_set = @tag_set
|
44
|
+
assert_equal expected, @weighted_group.filtered_similar_to(18, exclude: ['mod5', 'mod7'])
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_filters_include_and_exclude_recommendations
|
48
|
+
expected = [
|
49
|
+
{resource: '16', similarity: 0.8},
|
50
|
+
{resource: '4', similarity: 0.667},
|
51
|
+
{resource: '12', similarity: 0.333}
|
52
|
+
]
|
53
|
+
@weighted_group.tag_set = @tag_set
|
54
|
+
assert_equal expected, @weighted_group.filtered_similar_to(8, include: ['mod4'], exclude: ['mod5'])
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_filters_include_and_exclude_recommendations_and_limits
|
58
|
+
expected = [
|
59
|
+
{resource: '16', similarity: 0.8},
|
60
|
+
{resource: '4', similarity: 0.667},
|
61
|
+
{resource: '12', similarity: 0.333}
|
62
|
+
]
|
63
|
+
@weighted_group.tag_set = @tag_set
|
64
|
+
assert_equal expected[0..0], @weighted_group.filtered_similar_to(8, include: ['mod4'], exclude: ['mod5'], limit: 1)
|
65
|
+
assert_equal expected[0..1], @weighted_group.filtered_similar_to(8, include: ['mod4'], exclude: ['mod5'], limit: 2)
|
66
|
+
assert_equal expected, @weighted_group.filtered_similar_to(8, include: ['mod4'], exclude: ['mod5'], limit: 3)
|
67
|
+
assert_equal expected, @weighted_group.filtered_similar_to(8, include: ['mod4'], exclude: ['mod5'], limit: 99)
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_similar_to_mutliple_items
|
71
|
+
expected = [
|
72
|
+
{resource: '12', similarity: 78.437},
|
73
|
+
{resource: '18', similarity: 78.037},
|
74
|
+
{resource: '9', similarity: 11.67},
|
75
|
+
{resource: '21', similarity: 9.0},
|
76
|
+
{resource: '15', similarity: 9.0},
|
77
|
+
{resource: '6', similarity: 6.67},
|
78
|
+
{resource: '3', similarity: 6.67},
|
79
|
+
{resource: '8', similarity: 0.667},
|
80
|
+
{resource: '16', similarity: 0.5},
|
81
|
+
{resource: '20', similarity: 0.4}
|
82
|
+
]
|
83
|
+
@weighted_group.tag_set = @tag_set
|
84
|
+
assert_equal expected, @weighted_group.similar_to([3, 4, 5, 6, 7])
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_filtered_similar_to_mutliple_items
|
88
|
+
expected = [
|
89
|
+
{resource: '12', similarity: 78.437},
|
90
|
+
{resource: '8', similarity: 0.667},
|
91
|
+
{resource: '16', similarity: 0.5},
|
92
|
+
]
|
93
|
+
|
94
|
+
@weighted_group.tag_set = @tag_set
|
95
|
+
assert_equal expected, @weighted_group.filtered_similar_to([3, 4, 5, 6, 7], include: ['mod4'], exclude: ['mod5'])
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_weightings_affect_scores
|
99
|
+
skip
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_precalculates
|
103
|
+
skip
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: commendo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rob Styles
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-12-
|
11
|
+
date: 2015-12-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: mysql2
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: progressbar
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -38,6 +52,20 @@ dependencies:
|
|
38
52
|
- - ">="
|
39
53
|
- !ruby/object:Gem::Version
|
40
54
|
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: slop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
41
69
|
- !ruby/object:Gem::Dependency
|
42
70
|
name: bundler
|
43
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,16 +136,20 @@ dependencies:
|
|
108
136
|
- - "~>"
|
109
137
|
- !ruby/object:Gem::Version
|
110
138
|
version: 5.0.8
|
111
|
-
description: A Jaccard-similarity recommender using Redis sets
|
139
|
+
description: A Jaccard-similarity recommender using Redis sets or MySQL
|
112
140
|
email:
|
113
141
|
- rob.styles@dynamicorange.com
|
114
142
|
executables:
|
143
|
+
- commendo-create-mysql-db
|
144
|
+
- commendo-create.sql
|
115
145
|
- commendo-find-identical-pairs
|
116
146
|
- commendo-load
|
117
147
|
- commendo-load-tags-tsv
|
118
148
|
- commendo-load-tsv
|
149
|
+
- commendo-load-tsv-mysql.rb
|
119
150
|
- commendo-recommendations-distribution
|
120
151
|
- commendo-similarity-distribution
|
152
|
+
- commendo-time-mysql.rb
|
121
153
|
extensions: []
|
122
154
|
extra_rdoc_files: []
|
123
155
|
files:
|
@@ -127,23 +159,44 @@ files:
|
|
127
159
|
- LICENSE.txt
|
128
160
|
- README.md
|
129
161
|
- Rakefile
|
162
|
+
- bin/commendo-create-mysql-db
|
163
|
+
- bin/commendo-create.sql
|
130
164
|
- bin/commendo-find-identical-pairs
|
131
165
|
- bin/commendo-load
|
132
166
|
- bin/commendo-load-tags-tsv
|
133
167
|
- bin/commendo-load-tsv
|
168
|
+
- bin/commendo-load-tsv-mysql.rb
|
134
169
|
- bin/commendo-recommendations-distribution
|
135
170
|
- bin/commendo-similarity-distribution
|
171
|
+
- bin/commendo-time-mysql.rb
|
136
172
|
- commendo.gemspec
|
137
173
|
- lib/commendo.rb
|
174
|
+
- lib/commendo/configuration.rb
|
138
175
|
- lib/commendo/content_set.rb
|
139
|
-
- lib/commendo/
|
140
|
-
- lib/commendo/
|
176
|
+
- lib/commendo/mysql-backed/content_set.rb
|
177
|
+
- lib/commendo/mysql-backed/tag_set.rb
|
178
|
+
- lib/commendo/mysql-backed/weighted_group.rb
|
179
|
+
- lib/commendo/redis-backed/content_set.rb
|
180
|
+
- lib/commendo/redis-backed/pair_comparison.lua
|
181
|
+
- lib/commendo/redis-backed/similarity.lua
|
182
|
+
- lib/commendo/redis-backed/tag_set.rb
|
183
|
+
- lib/commendo/redis-backed/weighted_group.rb
|
141
184
|
- lib/commendo/tag_set.rb
|
142
185
|
- lib/commendo/version.rb
|
143
186
|
- lib/commendo/weighted_group.rb
|
144
|
-
-
|
145
|
-
-
|
146
|
-
-
|
187
|
+
- lib/mysql2/client.rb
|
188
|
+
- model 2.mwb
|
189
|
+
- sql_model.mwb
|
190
|
+
- test/configuration_test.rb
|
191
|
+
- test/mysql_content_set_test.rb
|
192
|
+
- test/mysql_tag_set_test.rb
|
193
|
+
- test/mysql_weighted_group_test.rb
|
194
|
+
- test/redis_content_set_test.rb
|
195
|
+
- test/redis_tag_set_test.rb
|
196
|
+
- test/redis_weighted_group_test.rb
|
197
|
+
- test/tests_for_content_sets.rb
|
198
|
+
- test/tests_for_tag_sets.rb
|
199
|
+
- test/tests_for_weighted_groups.rb
|
147
200
|
homepage: ''
|
148
201
|
licenses:
|
149
202
|
- MIT
|
@@ -167,9 +220,16 @@ rubyforge_project:
|
|
167
220
|
rubygems_version: 2.5.0
|
168
221
|
signing_key:
|
169
222
|
specification_version: 4
|
170
|
-
summary: A Jaccard-similarity recommender using Redis sets
|
223
|
+
summary: A Jaccard-similarity recommender using Redis sets or MySQL
|
171
224
|
test_files:
|
172
|
-
- test/
|
173
|
-
- test/
|
174
|
-
- test/
|
225
|
+
- test/configuration_test.rb
|
226
|
+
- test/mysql_content_set_test.rb
|
227
|
+
- test/mysql_tag_set_test.rb
|
228
|
+
- test/mysql_weighted_group_test.rb
|
229
|
+
- test/redis_content_set_test.rb
|
230
|
+
- test/redis_tag_set_test.rb
|
231
|
+
- test/redis_weighted_group_test.rb
|
232
|
+
- test/tests_for_content_sets.rb
|
233
|
+
- test/tests_for_tag_sets.rb
|
234
|
+
- test/tests_for_weighted_groups.rb
|
175
235
|
has_rdoc:
|
data/test/content_set_test.rb
DELETED
@@ -1,408 +0,0 @@
|
|
1
|
-
gem 'minitest'
|
2
|
-
require 'minitest/autorun'
|
3
|
-
require 'minitest/pride'
|
4
|
-
require 'minitest/mock'
|
5
|
-
require 'mocha/setup'
|
6
|
-
require 'commendo'
|
7
|
-
|
8
|
-
module Commendo
|
9
|
-
|
10
|
-
class ContentSetTest < Minitest::Test
|
11
|
-
|
12
|
-
def setup
|
13
|
-
@redis = Redis.new(db: 15)
|
14
|
-
@redis.flushdb
|
15
|
-
@key_base = 'CommendoTests'
|
16
|
-
@cs = ContentSet.new(@redis, @key_base)
|
17
|
-
end
|
18
|
-
|
19
|
-
def test_gives_similarity_key_for_resource
|
20
|
-
key_base = 'CommendoTestsFooBarBaz'
|
21
|
-
cs = ContentSet.new(@redis, key_base)
|
22
|
-
assert_equal 'CommendoTestsFooBarBaz:similar:resource-1', cs.similarity_key('resource-1')
|
23
|
-
end
|
24
|
-
|
25
|
-
def test_recommends_when_added
|
26
|
-
@cs.add('resource-1', 'group-1', 'group-2')
|
27
|
-
@cs.add('resource-2', 'group-1')
|
28
|
-
@cs.add('resource-3', 'group-1', 'group-2')
|
29
|
-
@cs.add('resource-4', 'group-2')
|
30
|
-
@cs.calculate_similarity
|
31
|
-
expected = [
|
32
|
-
{resource: 'resource-3', similarity: 1.0},
|
33
|
-
{resource: 'resource-4', similarity: 0.667},
|
34
|
-
{resource: 'resource-2', similarity: 0.667}
|
35
|
-
]
|
36
|
-
assert_equal expected, @cs.similar_to('resource-1')
|
37
|
-
end
|
38
|
-
|
39
|
-
def test_recommends_limited_by_number
|
40
|
-
@cs.add('resource-1', 'group-1', 'group-2')
|
41
|
-
@cs.add('resource-2', 'group-1')
|
42
|
-
@cs.add('resource-3', 'group-1', 'group-2')
|
43
|
-
@cs.add('resource-4', 'group-2')
|
44
|
-
@cs.calculate_similarity
|
45
|
-
expected = [
|
46
|
-
{resource: 'resource-3', similarity: 1.0},
|
47
|
-
{resource: 'resource-4', similarity: 0.667},
|
48
|
-
{resource: 'resource-2', similarity: 0.667}
|
49
|
-
]
|
50
|
-
assert_equal expected[0..0], @cs.similar_to('resource-1', 1)
|
51
|
-
assert_equal expected[0..1], @cs.similar_to('resource-1', 2)
|
52
|
-
assert_equal expected, @cs.similar_to('resource-1', 3)
|
53
|
-
assert_equal expected, @cs.similar_to('resource-1', 99)
|
54
|
-
end
|
55
|
-
|
56
|
-
def test_recommends_when_added_with_scores
|
57
|
-
@cs.add('resource-1', ['group-1', 2], ['group-2', 2])
|
58
|
-
@cs.add('resource-2', ['group-1', 7])
|
59
|
-
@cs.add('resource-3', ['group-1', 2], ['group-2', 2])
|
60
|
-
@cs.add('resource-4', ['group-2', 3])
|
61
|
-
@cs.calculate_similarity
|
62
|
-
expected = [
|
63
|
-
{resource: 'resource-3', similarity: 1.0},
|
64
|
-
{resource: 'resource-2', similarity: 0.818},
|
65
|
-
{resource: 'resource-4', similarity: 0.714}
|
66
|
-
]
|
67
|
-
assert_equal expected, @cs.similar_to('resource-1')
|
68
|
-
end
|
69
|
-
|
70
|
-
def test_recommends_with_large_number_of_groups
|
71
|
-
(0..3000).each do |i|
|
72
|
-
@cs.add('resource-1', ["group-#{i}", i/100.0], ["group-#{i+1}", i/20.0])
|
73
|
-
@cs.add('resource-9', ["group-#{i}", i/100.0], ["group-#{i+1}", i/20.0])
|
74
|
-
end
|
75
|
-
@cs.calculate_similarity
|
76
|
-
expected = [
|
77
|
-
{resource: 'resource-9', similarity: 1.0}
|
78
|
-
]
|
79
|
-
assert_equal expected, @cs.similar_to('resource-1')
|
80
|
-
end
|
81
|
-
|
82
|
-
def test_recommends_when_extra_scores_added
|
83
|
-
test_recommends_when_added_with_scores #sets up the content set
|
84
|
-
@cs.add('resource-3', ['group-1', 1], ['group-3', 2])
|
85
|
-
@cs.add('resource-4', ['group-2', 1])
|
86
|
-
@cs.add_by_group('group-1', ['newource-9', 100], 'resource-2', 'resource-3')
|
87
|
-
@cs.add_by_group('group-2', 'resource-1', 'resource-3', 'resource-4')
|
88
|
-
@cs.calculate_similarity
|
89
|
-
expected = [
|
90
|
-
{resource: 'newource-9', similarity: 1.0},
|
91
|
-
{resource: 'resource-1', similarity: 0.769},
|
92
|
-
{resource: 'resource-3', similarity: 0.706}
|
93
|
-
]
|
94
|
-
assert_equal expected, @cs.similar_to('resource-2')
|
95
|
-
end
|
96
|
-
|
97
|
-
def test_recommends_when_added_by_group
|
98
|
-
@cs.add_by_group('group-1', 'resource-1', 'resource-2', 'resource-3')
|
99
|
-
@cs.add_by_group('group-2', 'resource-1', 'resource-3', 'resource-4')
|
100
|
-
@cs.calculate_similarity
|
101
|
-
expected = [
|
102
|
-
{resource: 'resource-3', similarity: 1.0},
|
103
|
-
{resource: 'resource-4', similarity: 0.667},
|
104
|
-
{resource: 'resource-2', similarity: 0.667}
|
105
|
-
]
|
106
|
-
assert_equal expected, @cs.similar_to('resource-1')
|
107
|
-
end
|
108
|
-
|
109
|
-
def test_recommends_when_added_by_group_with_scores
|
110
|
-
@cs.add_by_group('group-1', ['resource-1', 2], ['resource-2', 3], ['resource-3', 7])
|
111
|
-
@cs.add_by_group('group-2', ['resource-1', 2], ['resource-3', 3], ['resource-4', 5])
|
112
|
-
@cs.calculate_similarity
|
113
|
-
expected = [
|
114
|
-
{resource: 'resource-3', similarity: 1.0},
|
115
|
-
{resource: 'resource-4', similarity: 0.778},
|
116
|
-
{resource: 'resource-2', similarity: 0.714}
|
117
|
-
]
|
118
|
-
assert_equal expected, @cs.similar_to('resource-1')
|
119
|
-
end
|
120
|
-
|
121
|
-
def test_recommendations_are_isolated_by_key_base
|
122
|
-
skip
|
123
|
-
end
|
124
|
-
|
125
|
-
def test_recommendations_are_isolated_by_redis_db
|
126
|
-
skip
|
127
|
-
end
|
128
|
-
|
129
|
-
def test_calculates_similarity_scores
|
130
|
-
(3..23).each do |group|
|
131
|
-
(3..23).each do |res|
|
132
|
-
@cs.add_by_group(group, res) if res % group == 0
|
133
|
-
end
|
134
|
-
end
|
135
|
-
@cs.calculate_similarity
|
136
|
-
expected = [
|
137
|
-
{resource: '9', similarity: 0.667},
|
138
|
-
{resource: '6', similarity: 0.667},
|
139
|
-
{resource: '12', similarity: 0.5},
|
140
|
-
{resource: '3', similarity: 0.4},
|
141
|
-
{resource: '21', similarity: 0.286},
|
142
|
-
{resource: '15', similarity: 0.286}
|
143
|
-
]
|
144
|
-
assert_equal expected, @cs.similar_to(18)
|
145
|
-
end
|
146
|
-
|
147
|
-
def test_calculates_with_threshold
|
148
|
-
(3..23).each do |group|
|
149
|
-
(3..23).each do |res|
|
150
|
-
@cs.add_by_group(group, res) if res % group == 0
|
151
|
-
end
|
152
|
-
end
|
153
|
-
@cs.calculate_similarity(0.4)
|
154
|
-
expected = [
|
155
|
-
{resource: '9', similarity: 0.667},
|
156
|
-
{resource: '6', similarity: 0.667},
|
157
|
-
{resource: '12', similarity: 0.5}
|
158
|
-
]
|
159
|
-
assert_equal expected, @cs.similar_to(18)
|
160
|
-
end
|
161
|
-
|
162
|
-
def test_calculate_copes_with_missing_resource
|
163
|
-
@cs.calculate_similarity_for_resource('999999999999', 0.1)
|
164
|
-
end
|
165
|
-
|
166
|
-
def test_calculate_yields_after_each
|
167
|
-
(3..23).each do |group|
|
168
|
-
(3..23).each do |res|
|
169
|
-
@cs.add_by_group(group, res) if res % group == 0
|
170
|
-
end
|
171
|
-
end
|
172
|
-
expected_keys = ['CommendoTests:resources:3', 'CommendoTests:resources:4', 'CommendoTests:resources:5', 'CommendoTests:resources:6', 'CommendoTests:resources:7', 'CommendoTests:resources:8', 'CommendoTests:resources:9', 'CommendoTests:resources:10', 'CommendoTests:resources:11', 'CommendoTests:resources:12', 'CommendoTests:resources:13', 'CommendoTests:resources:14', 'CommendoTests:resources:15', 'CommendoTests:resources:16', 'CommendoTests:resources:17', 'CommendoTests:resources:18', 'CommendoTests:resources:19', 'CommendoTests:resources:20', 'CommendoTests:resources:21', 'CommendoTests:resources:22', 'CommendoTests:resources:23']
|
173
|
-
actual_keys = []
|
174
|
-
@cs.calculate_similarity { |key, index, total|
|
175
|
-
actual_keys << key
|
176
|
-
}
|
177
|
-
assert_equal expected_keys.sort, actual_keys.sort
|
178
|
-
end
|
179
|
-
|
180
|
-
def test_deletes_resource_from_everywhere
|
181
|
-
(3..23).each do |group|
|
182
|
-
(3..23).each do |res|
|
183
|
-
@cs.add_by_group(group, res) if res % group == 0
|
184
|
-
end
|
185
|
-
end
|
186
|
-
@cs.calculate_similarity
|
187
|
-
assert similar_to(@cs, 18, 12)
|
188
|
-
|
189
|
-
@cs.delete(12)
|
190
|
-
assert_equal [], @cs.similar_to(12)
|
191
|
-
refute similar_to(@cs, 18, 12)
|
192
|
-
|
193
|
-
@cs.calculate_similarity
|
194
|
-
assert_equal [], @cs.similar_to(12)
|
195
|
-
refute similar_to(@cs, 18, 12)
|
196
|
-
end
|
197
|
-
|
198
|
-
def test_remove_from_groups
|
199
|
-
(3..23).each do |group|
|
200
|
-
(3..23).each do |res|
|
201
|
-
@cs.add(res, group) if res % group == 0
|
202
|
-
end
|
203
|
-
end
|
204
|
-
resource = 20
|
205
|
-
assert_equal ['4','5','10','20'].sort!, @cs.groups(resource).sort!
|
206
|
-
@cs.remove_from_groups(resource, 10)
|
207
|
-
assert_equal ['4','5','20'].sort!, @cs.groups(resource).sort!
|
208
|
-
@cs.remove_from_groups(resource, 4)
|
209
|
-
assert_equal ['5','20'].sort!, @cs.groups(resource).sort!
|
210
|
-
end
|
211
|
-
|
212
|
-
def test_remove_causes_similarity_to_change_when_recalculated
|
213
|
-
(3..23).each do |group|
|
214
|
-
(3..23).each do |res|
|
215
|
-
@cs.add(res, group) if res % group == 0
|
216
|
-
end
|
217
|
-
end
|
218
|
-
@cs.calculate_similarity
|
219
|
-
assert similar_to(@cs, 18, 12)
|
220
|
-
@cs.remove_from_groups(18, 6, 3)
|
221
|
-
@cs.calculate_similarity
|
222
|
-
refute similar_to(@cs, 18, 12)
|
223
|
-
end
|
224
|
-
|
225
|
-
def test_accepts_incremental_updates
|
226
|
-
(3..23).each do |group|
|
227
|
-
(3..23).each do |res|
|
228
|
-
@cs.add(res, group) if res % group == 0
|
229
|
-
end
|
230
|
-
end
|
231
|
-
@cs.calculate_similarity
|
232
|
-
assert similar_to(@cs, 18, 12)
|
233
|
-
refute similar_to(@cs, 10, 12)
|
234
|
-
|
235
|
-
@cs.add_and_calculate(12, 'foo', true)
|
236
|
-
@cs.add_and_calculate(10, 'foo', true)
|
237
|
-
assert similar_to(@cs, 10, 12)
|
238
|
-
end
|
239
|
-
|
240
|
-
def test_remove_and_calculate
|
241
|
-
(3..23).each do |group|
|
242
|
-
(3..23).each do |res|
|
243
|
-
@cs.add(res, group) if res % group == 0
|
244
|
-
end
|
245
|
-
end
|
246
|
-
@cs.calculate_similarity
|
247
|
-
assert similar_to(@cs, 18, 12)
|
248
|
-
@cs.remove_from_groups_and_calculate(18, 6, 3)
|
249
|
-
refute similar_to(@cs, 18, 12)
|
250
|
-
end
|
251
|
-
|
252
|
-
def test_filters_include_by_tag_collection
|
253
|
-
ts = TagSet.new(@redis, "#{@key_base}:tags")
|
254
|
-
@cs = ContentSet.new(@redis, @key_base, ts)
|
255
|
-
(3..23).each do |group|
|
256
|
-
(3..23).each do |res|
|
257
|
-
@cs.add(res, group) if res % group == 0
|
258
|
-
ts.add(res, 'mod3') if res.modulo(3).zero?
|
259
|
-
ts.add(res, 'mod4') if res.modulo(4).zero?
|
260
|
-
ts.add(res, 'mod5') if res.modulo(5).zero?
|
261
|
-
end
|
262
|
-
end
|
263
|
-
@cs.calculate_similarity
|
264
|
-
|
265
|
-
actual = @cs.filtered_similar_to(10, include: ['mod5'])
|
266
|
-
assert_equal 3, actual.length
|
267
|
-
assert contains_resource('5', actual)
|
268
|
-
assert contains_resource('15', actual)
|
269
|
-
assert contains_resource('20', actual)
|
270
|
-
|
271
|
-
end
|
272
|
-
|
273
|
-
def test_filters_include_by_tag_collection_and_limit
|
274
|
-
ts = TagSet.new(@redis, "#{@key_base}:tags")
|
275
|
-
@cs = ContentSet.new(@redis, @key_base, ts)
|
276
|
-
(3..23).each do |group|
|
277
|
-
(3..23).each do |res|
|
278
|
-
@cs.add(res, group) if res % group == 0
|
279
|
-
ts.add(res, 'mod3') if res.modulo(3).zero?
|
280
|
-
ts.add(res, 'mod4') if res.modulo(4).zero?
|
281
|
-
ts.add(res, 'mod5') if res.modulo(5).zero?
|
282
|
-
end
|
283
|
-
end
|
284
|
-
@cs.calculate_similarity
|
285
|
-
|
286
|
-
actual = @cs.filtered_similar_to(10, include: ['mod5'], limit: 2)
|
287
|
-
assert_equal 2, actual.length
|
288
|
-
assert contains_resource('5', actual)
|
289
|
-
#assert contains_resource('15', actual)
|
290
|
-
assert contains_resource('20', actual)
|
291
|
-
|
292
|
-
end
|
293
|
-
|
294
|
-
def test_filters_exclude_by_tag_collection
|
295
|
-
ts = TagSet.new(@redis, "#{@key_base}:tags")
|
296
|
-
@cs = ContentSet.new(@redis, @key_base, ts)
|
297
|
-
(3..23).each do |group|
|
298
|
-
(3..23).each do |res|
|
299
|
-
@cs.add(res, group) if res % group == 0
|
300
|
-
ts.add(res, 'mod3') if res.modulo(3).zero?
|
301
|
-
ts.add(res, 'mod4') if res.modulo(4).zero?
|
302
|
-
ts.add(res, 'mod5') if res.modulo(5).zero?
|
303
|
-
end
|
304
|
-
end
|
305
|
-
@cs.calculate_similarity
|
306
|
-
|
307
|
-
actual = @cs.filtered_similar_to(10, exclude: ['mod3'])
|
308
|
-
assert_equal 2, actual.length
|
309
|
-
assert contains_resource('5', actual)
|
310
|
-
assert contains_resource('20', actual)
|
311
|
-
refute contains_resource('15', actual)
|
312
|
-
end
|
313
|
-
|
314
|
-
def test_filters_includes_and_exclude_by_tag_collection
|
315
|
-
ts = TagSet.new(@redis, "#{@key_base}:tags")
|
316
|
-
@cs = ContentSet.new(@redis, @key_base, ts)
|
317
|
-
#Build some test data
|
318
|
-
(3..23).each do |group|
|
319
|
-
(3..23).each do |res|
|
320
|
-
@cs.add(res, group) if res % group == 0
|
321
|
-
ts.add(res, 'mod3') if res.modulo(3).zero?
|
322
|
-
ts.add(res, 'mod4') if res.modulo(4).zero?
|
323
|
-
ts.add(res, 'mod5') if res.modulo(5).zero?
|
324
|
-
end
|
325
|
-
end
|
326
|
-
@cs.calculate_similarity
|
327
|
-
|
328
|
-
actual = @cs.filtered_similar_to(12, include: ['mod4'], exclude: ['mod3', 'mod5'])
|
329
|
-
assert_equal 3, actual.length
|
330
|
-
|
331
|
-
refute contains_resource('6', actual)
|
332
|
-
refute contains_resource('18', actual)
|
333
|
-
assert contains_resource('4', actual)
|
334
|
-
refute contains_resource('3', actual)
|
335
|
-
refute contains_resource('9', actual)
|
336
|
-
assert contains_resource('8', actual)
|
337
|
-
refute contains_resource('21', actual)
|
338
|
-
assert contains_resource('16', actual)
|
339
|
-
refute contains_resource('15', actual)
|
340
|
-
refute contains_resource('20', actual)
|
341
|
-
end
|
342
|
-
|
343
|
-
def test_recommends_for_many
|
344
|
-
ts = TagSet.new(@redis, "#{@key_base}:tags")
|
345
|
-
@cs = ContentSet.new(@redis, @key_base, ts)
|
346
|
-
(3..23).each do |group|
|
347
|
-
(3..23).each do |res|
|
348
|
-
@cs.add(res, group) if res % group == 0
|
349
|
-
ts.add(res, 'mod3') if res.modulo(3).zero?
|
350
|
-
ts.add(res, 'mod4') if res.modulo(4).zero?
|
351
|
-
ts.add(res, 'mod5') if res.modulo(5).zero?
|
352
|
-
end
|
353
|
-
end
|
354
|
-
@cs.calculate_similarity
|
355
|
-
expected = [
|
356
|
-
{resource: '18', similarity: 1.834},
|
357
|
-
{resource: '3', similarity: 1.734},
|
358
|
-
{resource: '6', similarity: 1.167},
|
359
|
-
{resource: '21', similarity: 1.086},
|
360
|
-
{resource: '15', similarity: 1.086},
|
361
|
-
{resource: '12', similarity: 1.0},
|
362
|
-
{resource: '9', similarity: 0.833},
|
363
|
-
{resource: '4', similarity: 0.4},
|
364
|
-
{resource: '8', similarity: 0.333},
|
365
|
-
{resource: '16', similarity: 0.286},
|
366
|
-
{resource: '20', similarity: 0.25}
|
367
|
-
]
|
368
|
-
actual = @cs.similar_to([12, 6, 9])
|
369
|
-
assert_equal expected, actual
|
370
|
-
#, include: ['mod4'], exclude: ['mod3', 'mod5']
|
371
|
-
end
|
372
|
-
|
373
|
-
def test_recommends_for_many_applies_filters
|
374
|
-
ts = TagSet.new(@redis, "#{@key_base}:tags")
|
375
|
-
@cs = ContentSet.new(@redis, @key_base, ts)
|
376
|
-
(3..23).each do |group|
|
377
|
-
(3..23).each do |res|
|
378
|
-
@cs.add(res, group) if res % group == 0
|
379
|
-
ts.add(res, 'mod3') if res.modulo(3).zero?
|
380
|
-
ts.add(res, 'mod4') if res.modulo(4).zero?
|
381
|
-
ts.add(res, 'mod5') if res.modulo(5).zero?
|
382
|
-
end
|
383
|
-
end
|
384
|
-
@cs.calculate_similarity
|
385
|
-
actual = @cs.filtered_similar_to([12, 6, 9], include: ['mod4'], exclude: ['mod3', 'mod5'])
|
386
|
-
refute contains_resource('6', actual)
|
387
|
-
refute contains_resource('18', actual)
|
388
|
-
assert contains_resource('4', actual)
|
389
|
-
refute contains_resource('3', actual)
|
390
|
-
refute contains_resource('9', actual)
|
391
|
-
assert contains_resource('8', actual)
|
392
|
-
refute contains_resource('21', actual)
|
393
|
-
assert contains_resource('16', actual)
|
394
|
-
refute contains_resource('15', actual)
|
395
|
-
refute contains_resource('20', actual)
|
396
|
-
end
|
397
|
-
|
398
|
-
def similar_to(cs, resource, similar)
|
399
|
-
contains_resource(similar, cs.similar_to(resource))
|
400
|
-
end
|
401
|
-
|
402
|
-
def contains_resource(resource, similarities)
|
403
|
-
!similarities.select { |sim| sim[:resource] == "#{resource}" }.empty?
|
404
|
-
end
|
405
|
-
|
406
|
-
end
|
407
|
-
|
408
|
-
end
|