recommendations2 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Victor Rodrigues
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Recommendations
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'recommendations'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install recommendations
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,23 @@
1
+ module Recommendations
2
+ class Configuration
3
+ attr_accessor :redis
4
+ attr_writer :similar_items_count, :item_tolerance
5
+ attr_writer :similar_users_count, :user_tolerance
6
+
7
+ def similar_items_count
8
+ @similar_items_count || 10
9
+ end
10
+
11
+ def similar_users_count
12
+ @similar_items_count || 10
13
+ end
14
+
15
+ def item_tolerance
16
+ @item_tolerance || 30
17
+ end
18
+
19
+ def user_tolerance
20
+ @user_tolerance || 1
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,9 @@
1
+ module Recommendations
2
+ class Item
3
+ module Ratings
4
+ def ratings(rating, tolerance = Recommendations.configuration.user_tolerance)
5
+ Recommendations.redis.zrevrangebyscore("recommendations:ratings:item:#{id}", Float(rating) + tolerance, Float(rating) - tolerance)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,22 @@
1
+ module Recommendations
2
+ class Item
3
+ module Similarity
4
+ def similars(count = Recommendations.configuration.similar_users_count)
5
+ Recommendations.redis.zrange("recommendations:similars:item:#{id}", 0, count - 1)
6
+ end
7
+
8
+ def update_similars
9
+ Recommendations.redis.del("recommendations:similars:item:#{id}")
10
+
11
+ categories.each do |category, score|
12
+ items = Recommendations.redis.hgetall("recommendations:categories:#{category}")
13
+
14
+ items.reject {|k, v| k == id}.each do |item, item_score|
15
+ difference = (Float(item_score) - Float(score)).abs
16
+ Recommendations.redis.zincrby("recommendations:similars:item:#{id}", difference, item)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,30 @@
1
+ module Recommendations
2
+ class Item
3
+ include Item::Ratings
4
+ include Item::Similarity
5
+
6
+ attr_accessor :id
7
+ attr_writer :categories
8
+
9
+ def initialize(id, categories = {})
10
+ @id, @categories = id, categories
11
+ end
12
+
13
+ def categories
14
+ @categories || {}
15
+ end
16
+
17
+ def save!
18
+ Recommendations.redis.hmset("recommendations:item:#{id}", *categories.flatten)
19
+
20
+ categories.each do |category, score|
21
+ Recommendations.redis.hset("recommendations:categories:#{category}", id, score)
22
+ end
23
+ end
24
+
25
+ def self.find(id)
26
+ categories = Recommendations.redis.hgetall("recommendations:item:#{id}")
27
+ new(id, categories)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,14 @@
1
+ module Recommendations
2
+ class User
3
+ module Ratings
4
+ def rate(item_id, rating)
5
+ Recommendations.redis.hset("recommendations:ratings:user:#{id}", item_id, rating)
6
+ Recommendations.redis.zadd("recommendations:ratings:item:#{item_id}", rating, id)
7
+ end
8
+
9
+ def ratings
10
+ Recommendations.redis.hgetall("recommendations:ratings:user:#{id}")
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,38 @@
1
+ module Recommendations
2
+ class User
3
+ module Similarity
4
+ def similars(count = Recommendations.configuration.similar_items_count)
5
+ Recommendations.redis.zrevrange("recommendations:similars:user:#{id}", 0, count - 1)
6
+ end
7
+
8
+ def update_similars
9
+ Recommendations.redis.del("recommendations:similars:user:#{id}")
10
+
11
+ similar_users = {}
12
+
13
+ ratings.each do |item_id, rating|
14
+ similars = Item.find(item_id).ratings(rating)
15
+
16
+ similars.reject {|uid| uid == id}.each do |user_id|
17
+ user_rating = Recommendations.redis.zscore("recommendations:ratings:item:#{item_id}", user_id)
18
+ difference = (Float(user_rating) - Float(rating)).abs
19
+
20
+ similar_users[user_id] ||= [0,0]
21
+ similar_users[user_id][0] += difference
22
+ similar_users[user_id][1] += 1
23
+ end
24
+ end
25
+
26
+ similar_users.each do |user_id, (difference, common_items)|
27
+ difference_factor = Float(Recommendations.configuration.user_tolerance) - Float(difference)/Float(common_items)
28
+ common_items_factor = 0.2 * Float(common_items)
29
+ similar_users[user_id] = difference_factor + common_items_factor
30
+ end
31
+
32
+ similar_users.each do |user_id, score|
33
+ Recommendations.redis.zincrby("recommendations:similars:user:#{id}", score, user_id)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,40 @@
1
+ module Recommendations
2
+ class User
3
+ module Suggestions
4
+ def suggestions
5
+ Recommendations.redis.hgetall("recommendations:suggestions:user:#{id}")
6
+ .select {|key, value| Float(value) > 0}
7
+ end
8
+
9
+ def update_suggestions
10
+ ratings = self.ratings
11
+
12
+ suggestions = ratings.inject({}) do |buffer, (item_id, score)|
13
+ similars = Item.find(item_id).similars
14
+ similars.reject! {|i| ratings.key?(i)}
15
+
16
+ similars.each do |similar|
17
+ buffer[similar] ||= 0
18
+ buffer[similar] += (Float(score) - 3) # 0..5 score => -2..2
19
+ end
20
+
21
+ buffer
22
+ end
23
+
24
+ #
25
+ self.similars.each do |similar| #similars users
26
+ #remover os ratings q o usuário já fez
27
+ similar_ratings = User.new(similar).ratings
28
+ similar_ratings.reject! {|i| ratings.key?(i.key)}
29
+
30
+ similar_ratings.each do |(item_id, score)| #user ratings
31
+ suggestions[item_id] ||= 0
32
+ suggestions[item_id] += (Float(score) - 3) # 0..5 score => -2..2
33
+ end
34
+ end
35
+ #
36
+ Recommendations.redis.hmset("recommendations:suggestions:user:#{id}", *suggestions.flatten)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,13 @@
1
+ module Recommendations
2
+ class User
3
+ include User::Ratings
4
+ include User::Similarity
5
+ include User::Suggestions
6
+
7
+ attr_accessor :id
8
+
9
+ def initialize(id)
10
+ @id = id
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ module Recommendations
2
+ VERSION = "0.0.4"
3
+ end
@@ -0,0 +1,29 @@
1
+ require "redis"
2
+
3
+ require "recommendations/version"
4
+ require "recommendations/configuration"
5
+ require "recommendations/user/ratings"
6
+ require "recommendations/user/similarity"
7
+ require "recommendations/user/suggestions"
8
+ require "recommendations/user"
9
+ require "recommendations/item/ratings"
10
+ require "recommendations/item/similarity"
11
+ require "recommendations/item"
12
+
13
+ module Recommendations
14
+ extend self
15
+
16
+ def configuration
17
+ @configuration ||= Configuration.new
18
+ end
19
+
20
+ def configure(&block)
21
+ configuration.instance_eval(&block)
22
+ end
23
+
24
+ def redis
25
+ @redis ||= begin
26
+ configuration.redis ? Redis.new(configuration.redis) : Redis.new
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path("../lib/recommendations/version", __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Victor Rodrigues", "Thiago Ferreira"]
6
+ gem.email = ["victorc.rodrigues@gmail.com", "ferreira07@gmail.com"]
7
+ gem.description = "recommendations between similar items"
8
+ gem.summary = ""
9
+ gem.homepage = ""
10
+
11
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
+ gem.files = `git ls-files`.split("\n")
13
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
+ gem.name = "recommendations2"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Recommendations::VERSION
17
+ gem.add_development_dependency "rspec", "~> 2.10"
18
+ gem.add_runtime_dependency "redis"
19
+ end
@@ -0,0 +1,25 @@
1
+ require "spec_helper"
2
+
3
+ describe Recommendations::Item::Ratings do
4
+ describe "#ratings" do
5
+ subject {Recommendations::Item.new("1")}
6
+
7
+ let :ratings do
8
+ {
9
+ "1" => "5",
10
+ "2" => "4",
11
+ "3" => "3",
12
+ "4" => "4",
13
+ "5" => "5"
14
+ }
15
+ end
16
+
17
+ before do
18
+ ratings.each {|k, v| Recommendations::User.new(k).rate("1", v)}
19
+ end
20
+
21
+ it "returns all ratings" do
22
+ subject.ratings(5, 1).should == %w(5 1 4 2)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,32 @@
1
+ require "spec_helper"
2
+
3
+ describe Recommendations::Item do
4
+ subject {Recommendations::Item.new("1", violent: 100, romantic: 25)}
5
+
6
+ describe "#initialize" do
7
+ its(:id) {should == "1"}
8
+ its(:categories) {should == {violent: 100, romantic: 25}}
9
+ end
10
+
11
+ describe "#similars" do
12
+ context "when count was not specified" do
13
+ let(:count) {Recommendations.configuration.similar_items_count - 1}
14
+
15
+ it "gets sorted set range from similar count configuration" do
16
+ Recommendations.redis.should_receive(:zrange)
17
+ .with("recommendations:similars:item:1", 0, count)
18
+ .and_return ["2", "3", "4"]
19
+ subject.similars.should == ["2", "3", "4"]
20
+ end
21
+ end
22
+
23
+ context "when count was specified" do
24
+ it "gets sorted set range until count" do
25
+ Recommendations.redis.should_receive(:zrange)
26
+ .with("recommendations:similars:item:1", 0, 9)
27
+ .and_return ["2", "3", "4"]
28
+ subject.similars(10).should == ["2", "3", "4"]
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,37 @@
1
+ require "spec_helper"
2
+
3
+ describe Recommendations::User::Ratings do
4
+ subject {Recommendations::User.new("1")}
5
+
6
+ describe "#rate" do
7
+ before {subject.rate("890", 4.5)}
8
+
9
+ it "stores a user rating for this item" do
10
+ Recommendations.redis.hget("recommendations:ratings:user:1", "890").should == "4.5"
11
+ end
12
+
13
+ it "stores an item rating for this user" do
14
+ Recommendations.redis.zscore("recommendations:ratings:item:890", "1").should == "4.5"
15
+ end
16
+ end
17
+
18
+ describe "#ratings" do
19
+ let :ratings do
20
+ {
21
+ "1" => "5",
22
+ "2" => "4",
23
+ "3" => "3",
24
+ "4" => "2",
25
+ "5" => "1"
26
+ }
27
+ end
28
+
29
+ before do
30
+ ratings.each {|k, v| subject.rate(k, v)}
31
+ end
32
+
33
+ it "returns all ratings" do
34
+ subject.ratings.should == ratings
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,48 @@
1
+ require "spec_helper"
2
+
3
+ describe Recommendations::User::Similarity do
4
+ describe "#update similarity" do
5
+ subject {Recommendations::User.new("1")}
6
+
7
+ let :ratings do
8
+ {
9
+ "1" => {
10
+ "1" => "5",
11
+ "2" => "4",
12
+ "3" => "3",
13
+ "4" => "2"
14
+ },
15
+ "2" => {
16
+ "1" => "5",
17
+ "2" => "4",
18
+ "3" => "2",
19
+ "4" => "1"
20
+ },
21
+ "3" => {
22
+ "1" => "1",
23
+ "2" => "2",
24
+ "3" => "1",
25
+ "4" => "1"
26
+ },
27
+ "4" => {
28
+ "1" => "4",
29
+ "2" => "3",
30
+ "3" => "2",
31
+ "4" => "1"
32
+ }
33
+ }
34
+ end
35
+
36
+ before do
37
+ ratings.each do |user_id, user_ratings|
38
+ user = Recommendations::User.new(user_id)
39
+ user_ratings.each {|item_id, rating| user.rate(item_id, rating)}
40
+ end
41
+ end
42
+
43
+ it "update similars" do
44
+ subject.update_similars
45
+ subject.similars.should == %w(2 4 3)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,25 @@
1
+ require "spec_helper"
2
+
3
+ describe Recommendations::User::Suggestions do
4
+ describe "#update_suggestions" do
5
+ subject {Recommendations::User.new("1")}
6
+
7
+ before do
8
+ i1 = Recommendations::Item.new("1", violento: 100, romantico: 0, acao: 50)
9
+ i2 = Recommendations::Item.new("2", violento: 50, romantico: 0, acao: 50) # 1.50
10
+ i3 = Recommendations::Item.new("3", violento: 100, romantico: 100, acao: 0) # 1.150, 2.200
11
+ i4 = Recommendations::Item.new("4", violento: 70, romantico: 30, acao: 20) # 1.90, 2.80
12
+
13
+ items = [i1, i2, i3, i4]
14
+ items.each(&:save!)
15
+ items.each(&:update_similars)
16
+
17
+ subject.rate("1", 5)
18
+ end
19
+
20
+ it "updates suggestions" do
21
+ subject.update_suggestions
22
+ subject.suggestions.should == {"2" => "2.0", "4" => "2.0", "3" => "2.0"}
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,5 @@
1
+ require "spec_helper"
2
+
3
+ describe Recommendations::User do
4
+
5
+ end
@@ -0,0 +1,5 @@
1
+ require "spec_helper"
2
+
3
+ describe Recommendations do
4
+
5
+ end
@@ -0,0 +1,8 @@
1
+ require "rspec"
2
+ require "recommendations"
3
+
4
+ RSpec.configure do |config|
5
+ config.before do
6
+ Recommendations.redis.flushdb
7
+ end
8
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: recommendations2
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.4
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Victor Rodrigues
9
+ - Thiago Ferreira
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2012-06-10 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rspec
17
+ requirement: &22145412 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: '2.10'
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: *22145412
26
+ - !ruby/object:Gem::Dependency
27
+ name: redis
28
+ requirement: &22145052 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: *22145052
37
+ description: recommendations between similar items
38
+ email:
39
+ - victorc.rodrigues@gmail.com
40
+ - ferreira07@gmail.com
41
+ executables: []
42
+ extensions: []
43
+ extra_rdoc_files: []
44
+ files:
45
+ - .gitignore
46
+ - Gemfile
47
+ - LICENSE
48
+ - README.md
49
+ - Rakefile
50
+ - lib/recommendations.rb
51
+ - lib/recommendations/configuration.rb
52
+ - lib/recommendations/item.rb
53
+ - lib/recommendations/item/ratings.rb
54
+ - lib/recommendations/item/similarity.rb
55
+ - lib/recommendations/user.rb
56
+ - lib/recommendations/user/ratings.rb
57
+ - lib/recommendations/user/similarity.rb
58
+ - lib/recommendations/user/suggestions.rb
59
+ - lib/recommendations/version.rb
60
+ - recommendations.gemspec
61
+ - spec/recommendations/item/ratings_spec.rb
62
+ - spec/recommendations/item_spec.rb
63
+ - spec/recommendations/user/ratings_spec.rb
64
+ - spec/recommendations/user/similarity_spec.rb
65
+ - spec/recommendations/user/suggestions_spec.rb
66
+ - spec/recommendations/user_spec.rb
67
+ - spec/recommendations_spec.rb
68
+ - spec/spec_helper.rb
69
+ homepage: ''
70
+ licenses: []
71
+ post_install_message:
72
+ rdoc_options: []
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ! '>='
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ! '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ requirements: []
88
+ rubyforge_project:
89
+ rubygems_version: 1.8.16
90
+ signing_key:
91
+ specification_version: 3
92
+ summary: ''
93
+ test_files: []