commendo 0.0.9 → 1.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-load +22 -13
- data/commendo.gemspec +1 -0
- data/lib/commendo/content_set.rb +16 -8
- data/lib/commendo/tag_set.rb +4 -2
- data/lib/commendo/version.rb +1 -1
- data/lib/commendo/weighted_group.rb +15 -7
- data/test/content_set_test.rb +44 -0
- data/test/tag_set_test.rb +57 -12
- data/test/weighted_group_test.rb +42 -0
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f4d4570da8f16342e587461c8731982f4a82c8ba
|
4
|
+
data.tar.gz: bdde9ae21c9bb35c7c9b6073a82e651086132617
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 31406c7ee3846a6046e8e149ca8ada66eaed799977ee615e778a5fb3a2c353165e246deb87b422bef7d5a544a4dddbac3788a4ed879a77a04b13b0f3ed9d7dbc
|
7
|
+
data.tar.gz: 15e58e641bd237466b80713c64d893ebd66877aa4c4a28241b584875d5e476fc28c45d68194eacd9b74cfef5a4e144c011e892185bd1ee9d27f38fc346c11032
|
data/CHANGELOG.md
CHANGED
data/bin/commendo-load
CHANGED
@@ -1,21 +1,30 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
redis_db = ARGV[0].to_i
|
4
|
-
content_set_base_redis_key = ARGV[1]
|
5
|
-
filename = ARGV[2]
|
6
|
-
|
7
3
|
require 'redis'
|
8
4
|
require 'commendo'
|
5
|
+
require 'json'
|
9
6
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
File.open(filename) do |f|
|
7
|
+
filename = ARGV[0]
|
8
|
+
redis_db = ARGV[1].to_i
|
9
|
+
base_key = ARGV[2]
|
14
10
|
|
15
|
-
|
16
|
-
|
11
|
+
redis = Redis.new(db: redis_db, timeout: 60)
|
12
|
+
cs = Commendo::ContentSet.new(redis, base_key)
|
17
13
|
|
18
|
-
|
19
|
-
|
14
|
+
puts "Loading."
|
15
|
+
File.open(filename) do |f|
|
16
|
+
f.each_line.with_index do |json, i|
|
17
|
+
print '.'
|
18
|
+
resource, scored_groups = JSON.parse(json)
|
19
|
+
cs.add(resource, *scored_groups)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
puts "\nFinished loading"
|
20
23
|
|
21
|
-
|
24
|
+
puts 'Calculating similarities'
|
25
|
+
pbar = nil
|
26
|
+
cs.calculate_similarity do |key, i, total|
|
27
|
+
pbar ||= ProgressBar.new('Calculating similarity', total)
|
28
|
+
pbar.inc
|
29
|
+
#puts "Calculating similarity for #{i}/#{total} — #{key}"
|
30
|
+
end
|
data/commendo.gemspec
CHANGED
data/lib/commendo/content_set.rb
CHANGED
@@ -85,17 +85,18 @@ module Commendo
|
|
85
85
|
end
|
86
86
|
end
|
87
87
|
|
88
|
-
def similar_to(resource)
|
88
|
+
def similar_to(resource, limit = 0)
|
89
|
+
finish = limit -1
|
89
90
|
if resource.kind_of? Array
|
90
91
|
keys = resource.map do |res|
|
91
92
|
similarity_key(res)
|
92
93
|
end
|
93
94
|
tmp_key = "#{key_base}:tmp:#{SecureRandom.uuid}"
|
94
95
|
redis.zunionstore(tmp_key, keys)
|
95
|
-
similar_resources = redis.zrevrange(tmp_key, 0,
|
96
|
+
similar_resources = redis.zrevrange(tmp_key, 0, finish, with_scores: true)
|
96
97
|
redis.del(tmp_key)
|
97
98
|
else
|
98
|
-
similar_resources = redis.zrevrange(similarity_key(resource), 0,
|
99
|
+
similar_resources = redis.zrevrange(similarity_key(resource), 0, finish, with_scores: true)
|
99
100
|
end
|
100
101
|
similar_resources.map do |resource|
|
101
102
|
{resource: resource[0], similarity: resource[1].to_f}
|
@@ -103,11 +104,18 @@ module Commendo
|
|
103
104
|
end
|
104
105
|
|
105
106
|
def filtered_similar_to(resource, options = {})
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
107
|
+
if @tag_set.nil? || (options[:include].nil? && options[:exclude].nil?)
|
108
|
+
return similar_to(resource, options[:limit] || 0)
|
109
|
+
else
|
110
|
+
similar = similar_to(resource)
|
111
|
+
limit = options[:limit] || similar.length
|
112
|
+
filtered = []
|
113
|
+
similar.each do |s|
|
114
|
+
return filtered if filtered.length >= limit
|
115
|
+
filtered << s if @tag_set.matches(s[:resource], options[:include], options[:exclude])
|
116
|
+
end
|
117
|
+
return filtered
|
118
|
+
end
|
111
119
|
end
|
112
120
|
|
113
121
|
def similarity_key(resource)
|
data/lib/commendo/tag_set.rb
CHANGED
@@ -21,9 +21,11 @@ module Commendo
|
|
21
21
|
add(resource, *tags)
|
22
22
|
end
|
23
23
|
|
24
|
-
def matches(resource,
|
24
|
+
def matches(resource, include, exclude = [])
|
25
25
|
resource_tags = get(resource)
|
26
|
-
(resource_tags &
|
26
|
+
can_include = include.nil? || include.empty? || (resource_tags & include).length > 0
|
27
|
+
should_exclude = !exclude.nil? && !exclude.empty? && (resource_tags & exclude).length > 0
|
28
|
+
return can_include && !should_exclude
|
27
29
|
end
|
28
30
|
|
29
31
|
def delete(resource)
|
data/lib/commendo/version.rb
CHANGED
@@ -8,7 +8,8 @@ module Commendo
|
|
8
8
|
@content_sets, @redis, @key_base = content_sets, redis, key_base
|
9
9
|
end
|
10
10
|
|
11
|
-
def similar_to(resource)
|
11
|
+
def similar_to(resource, limit = 0)
|
12
|
+
finish = limit -1
|
12
13
|
resources = resource.kind_of?(Array) ? resource : [resource]
|
13
14
|
keys = []
|
14
15
|
weights = []
|
@@ -20,7 +21,7 @@ module Commendo
|
|
20
21
|
end
|
21
22
|
tmp_key = "#{key_base}:tmp:#{SecureRandom.uuid}"
|
22
23
|
redis.zunionstore(tmp_key, keys, weights: weights)
|
23
|
-
similar_resources = redis.zrevrange(tmp_key, 0,
|
24
|
+
similar_resources = redis.zrevrange(tmp_key, 0, finish, with_scores: true)
|
24
25
|
redis.del(tmp_key)
|
25
26
|
|
26
27
|
similar_resources.map do |resource|
|
@@ -30,11 +31,18 @@ module Commendo
|
|
30
31
|
end
|
31
32
|
|
32
33
|
def filtered_similar_to(resource, options = {})
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
34
|
+
if @tag_set.nil? || (options[:include].nil? && options[:exclude].nil?)
|
35
|
+
return similar_to(resource, options[:limit] || 0)
|
36
|
+
else
|
37
|
+
similar = similar_to(resource)
|
38
|
+
limit = options[:limit] || similar.length
|
39
|
+
filtered = []
|
40
|
+
similar.each do |s|
|
41
|
+
return filtered if filtered.length >= limit
|
42
|
+
filtered << s if @tag_set.matches(s[:resource], options[:include], options[:exclude])
|
43
|
+
end
|
44
|
+
return filtered
|
45
|
+
end
|
38
46
|
end
|
39
47
|
|
40
48
|
end
|
data/test/content_set_test.rb
CHANGED
@@ -34,6 +34,27 @@ module Commendo
|
|
34
34
|
assert_equal expected, cs.similar_to('resource-1')
|
35
35
|
end
|
36
36
|
|
37
|
+
def test_recommends_limited_by_number
|
38
|
+
redis = Redis.new(db: 15)
|
39
|
+
redis.flushdb
|
40
|
+
key_base = 'CommendoTests'
|
41
|
+
cs = ContentSet.new(redis, key_base)
|
42
|
+
cs.add('resource-1', 'group-1', 'group-2')
|
43
|
+
cs.add('resource-2', 'group-1')
|
44
|
+
cs.add('resource-3', 'group-1', 'group-2')
|
45
|
+
cs.add('resource-4', 'group-2')
|
46
|
+
cs.calculate_similarity
|
47
|
+
expected = [
|
48
|
+
{resource: 'resource-3', similarity: 1.0},
|
49
|
+
{resource: 'resource-4', similarity: 0.667},
|
50
|
+
{resource: 'resource-2', similarity: 0.667}
|
51
|
+
]
|
52
|
+
assert_equal expected[0..0], cs.similar_to('resource-1', 1)
|
53
|
+
assert_equal expected[0..1], cs.similar_to('resource-1', 2)
|
54
|
+
assert_equal expected, cs.similar_to('resource-1', 3)
|
55
|
+
assert_equal expected, cs.similar_to('resource-1', 99)
|
56
|
+
end
|
57
|
+
|
37
58
|
def test_recommends_when_added_with_scores
|
38
59
|
redis = Redis.new(db: 15)
|
39
60
|
redis.flushdb
|
@@ -254,6 +275,29 @@ module Commendo
|
|
254
275
|
|
255
276
|
end
|
256
277
|
|
278
|
+
def test_filters_include_by_tag_collection_and_limit
|
279
|
+
redis = Redis.new(db: 15)
|
280
|
+
redis.flushdb
|
281
|
+
ts = TagSet.new(redis, 'CommendoTests:tags')
|
282
|
+
cs = ContentSet.new(redis, 'CommendoTests', ts)
|
283
|
+
(3..23).each do |group|
|
284
|
+
(3..23).each do |res|
|
285
|
+
cs.add(res, group) if res % group == 0
|
286
|
+
ts.add(res, 'mod3') if res.modulo(3).zero?
|
287
|
+
ts.add(res, 'mod4') if res.modulo(4).zero?
|
288
|
+
ts.add(res, 'mod5') if res.modulo(5).zero?
|
289
|
+
end
|
290
|
+
end
|
291
|
+
cs.calculate_similarity
|
292
|
+
|
293
|
+
actual = cs.filtered_similar_to(10, include: ['mod5'], limit: 2)
|
294
|
+
assert_equal 2, actual.length
|
295
|
+
assert contains_resource('5', actual)
|
296
|
+
#assert contains_resource('15', actual)
|
297
|
+
assert contains_resource('20', actual)
|
298
|
+
|
299
|
+
end
|
300
|
+
|
257
301
|
def test_filters_exclude_by_tag_collection
|
258
302
|
redis = Redis.new(db: 15)
|
259
303
|
redis.flushdb
|
data/test/tag_set_test.rb
CHANGED
@@ -64,18 +64,63 @@ module Commendo
|
|
64
64
|
ts.set(1, 'foo', 'bar', 'baz')
|
65
65
|
ts.set(2, 'qux', 'qip')
|
66
66
|
|
67
|
-
assert ts.matches(1, 'foo')
|
68
|
-
assert ts.matches(1, 'bar', 'baz')
|
69
|
-
assert ts.matches(1, 'bar', 'baz', 'foo')
|
70
|
-
refute ts.matches(1, 'qux')
|
71
|
-
refute ts.matches(1, 'qip')
|
72
|
-
|
73
|
-
refute ts.matches(2, 'foo')
|
74
|
-
refute ts.matches(2, 'bar', 'baz')
|
75
|
-
refute ts.matches(2, 'bar', 'baz', 'foo')
|
76
|
-
assert ts.matches(2, 'qux', 'qip')
|
77
|
-
assert ts.matches(2, 'qux')
|
78
|
-
assert ts.matches(2, 'qip')
|
67
|
+
assert ts.matches(1, ['foo'])
|
68
|
+
assert ts.matches(1, ['bar', 'baz'])
|
69
|
+
assert ts.matches(1, ['bar', 'baz', 'foo'])
|
70
|
+
refute ts.matches(1, ['qux'])
|
71
|
+
refute ts.matches(1, ['qip'])
|
72
|
+
|
73
|
+
refute ts.matches(2, ['foo'])
|
74
|
+
refute ts.matches(2, ['bar', 'baz'])
|
75
|
+
refute ts.matches(2, ['bar', 'baz', 'foo'])
|
76
|
+
assert ts.matches(2, ['qux', 'qip'])
|
77
|
+
assert ts.matches(2, ['qux'])
|
78
|
+
assert ts.matches(2, ['qip'])
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_matches_exclude_tags
|
82
|
+
redis = Redis.new(db: 15)
|
83
|
+
redis.flushdb
|
84
|
+
ts = TagSet.new(redis, 'TagSetTest')
|
85
|
+
ts.set(1, 'foo', 'bar', 'baz')
|
86
|
+
ts.set(2, 'qux', 'qip')
|
87
|
+
|
88
|
+
refute ts.matches(1, nil, ['foo'])
|
89
|
+
refute ts.matches(1, [], ['foo'])
|
90
|
+
refute ts.matches(1, [], ['bar', 'baz'])
|
91
|
+
refute ts.matches(1, [], ['bar', 'baz', 'foo'])
|
92
|
+
assert ts.matches(1, [], ['qux'])
|
93
|
+
assert ts.matches(1, [], ['qip'])
|
94
|
+
|
95
|
+
assert ts.matches(2, nil, ['foo'])
|
96
|
+
assert ts.matches(2, [], ['foo'])
|
97
|
+
assert ts.matches(2, [], ['bar', 'baz'])
|
98
|
+
assert ts.matches(2, [], ['bar', 'baz', 'foo'])
|
99
|
+
refute ts.matches(2, [], ['qux', 'qip'])
|
100
|
+
refute ts.matches(2, [], ['qux'])
|
101
|
+
refute ts.matches(2, [], ['qip'])
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_matches_include_and_exclude_tags
|
105
|
+
redis = Redis.new(db: 15)
|
106
|
+
redis.flushdb
|
107
|
+
ts = TagSet.new(redis, 'TagSetTest')
|
108
|
+
ts.set(1, 'foo', 'bar', 'baz')
|
109
|
+
ts.set(2, 'qux', 'qip')
|
110
|
+
|
111
|
+
refute ts.matches(1, ['foo'], ['bar'])
|
112
|
+
refute ts.matches(1, ['bar'], ['foo'])
|
113
|
+
|
114
|
+
assert ts.matches(1, ['foo'], [])
|
115
|
+
assert ts.matches(1, ['foo'], nil)
|
116
|
+
assert ts.matches(1, ['foo'], ['qux'])
|
117
|
+
|
118
|
+
assert ts.matches(2, ['qip'], ['foo'])
|
119
|
+
assert ts.matches(2, ['qux'], ['bar', 'baz'])
|
120
|
+
assert ts.matches(2, ['qip'], ['bar', 'baz', 'foo'])
|
121
|
+
refute ts.matches(2, ['qip'], ['qux', 'qip'])
|
122
|
+
refute ts.matches(2, ['qip'], ['qux'])
|
123
|
+
refute ts.matches(2, ['qux'], ['qip'])
|
79
124
|
end
|
80
125
|
|
81
126
|
end
|
data/test/weighted_group_test.rb
CHANGED
@@ -50,6 +50,28 @@ module Commendo
|
|
50
50
|
assert_equal expected, weighted_group.similar_to(18)
|
51
51
|
end
|
52
52
|
|
53
|
+
def test_calls_each_content_set_with_limits
|
54
|
+
weighted_group = WeightedGroup.new(
|
55
|
+
@redis,
|
56
|
+
'CommendoTests:WeightedGroup',
|
57
|
+
{cs: @cs1, weight: 1.0},
|
58
|
+
{cs: @cs2, weight: 10.0},
|
59
|
+
{cs: @cs3, weight: 100.0}
|
60
|
+
)
|
61
|
+
expected = [
|
62
|
+
{resource: '6', similarity: 74.037},
|
63
|
+
{resource: '12', similarity: 55.5},
|
64
|
+
{resource: '9', similarity: 6.67},
|
65
|
+
{resource: '3', similarity: 4.0},
|
66
|
+
{resource: '21', similarity: 2.86},
|
67
|
+
{resource: '15', similarity: 2.86}
|
68
|
+
]
|
69
|
+
assert_equal expected[0..0], weighted_group.similar_to(18, 1)
|
70
|
+
assert_equal expected[0..2], weighted_group.similar_to(18, 3)
|
71
|
+
assert_equal expected, weighted_group.similar_to(18, 6)
|
72
|
+
assert_equal expected, weighted_group.similar_to(18, 99)
|
73
|
+
end
|
74
|
+
|
53
75
|
def test_filters_include_recommendations
|
54
76
|
weighted_group = WeightedGroup.new(
|
55
77
|
@redis,
|
@@ -98,6 +120,26 @@ module Commendo
|
|
98
120
|
assert_equal expected, weighted_group.filtered_similar_to(8, include: ['mod4'], exclude: ['mod5'])
|
99
121
|
end
|
100
122
|
|
123
|
+
def test_filters_include_and_exclude_recommendations_and_limits
|
124
|
+
weighted_group = WeightedGroup.new(
|
125
|
+
@redis,
|
126
|
+
'CommendoTests:WeightedGroup',
|
127
|
+
{cs: @cs1, weight: 100.0},
|
128
|
+
{cs: @cs2, weight: 10.0},
|
129
|
+
{cs: @cs3, weight: 1.0}
|
130
|
+
)
|
131
|
+
expected = [
|
132
|
+
{resource: '16', similarity: 80.0},
|
133
|
+
{resource: '4', similarity: 66.7},
|
134
|
+
{resource: '12', similarity: 33.3}
|
135
|
+
]
|
136
|
+
weighted_group.tag_set = @tag_set
|
137
|
+
assert_equal expected[0..0], weighted_group.filtered_similar_to(8, include: ['mod4'], exclude: ['mod5'], limit: 1)
|
138
|
+
assert_equal expected[0..1], weighted_group.filtered_similar_to(8, include: ['mod4'], exclude: ['mod5'], limit: 2)
|
139
|
+
assert_equal expected, weighted_group.filtered_similar_to(8, include: ['mod4'], exclude: ['mod5'], limit: 3)
|
140
|
+
assert_equal expected, weighted_group.filtered_similar_to(8, include: ['mod4'], exclude: ['mod5'], limit: 99)
|
141
|
+
end
|
142
|
+
|
101
143
|
def test_similar_to_mutliple_items
|
102
144
|
weighted_group = WeightedGroup.new(
|
103
145
|
@redis,
|
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: 0.0
|
4
|
+
version: 1.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: 2014-04-
|
11
|
+
date: 2014-04-22 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: progressbar
|
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: bundler
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|