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