Skip to content

Commit

Permalink
rewrote algorithm in SQL for better performance
Browse files Browse the repository at this point in the history
  • Loading branch information
Diego Salazar committed Jan 25, 2017
1 parent 92ed456 commit 1af8aa4
Show file tree
Hide file tree
Showing 5 changed files with 28 additions and 62 deletions.
2 changes: 1 addition & 1 deletion app/controllers/recommendations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def index
end

format.json do
subtopic_ids = @recommendations.map &:first
subtopic_ids = @recommendations.map { |r| r["subtopic_id"] }
render json: subtopic_ids
end
end
Expand Down
11 changes: 11 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,14 @@ class User < ActiveRecord::Base
where listens: { subtopic_id: subtopic_id }
}
end

<<-SQL
SELECT subtopic_id, COUNT(id) AS listens_count FROM listens
WHERE listens.user_id IN (
SELECT users.user_id FROM users
RIGHT JOIN listens ON listens.user_id = users.user_id
WHERE listens.subtopic_id = '575f6209a06acf0300140150'
)
GROUP BY subtopic_id
ORDER BY listens_count DESC
SQL
39 changes: 12 additions & 27 deletions app/services/recommender.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,17 @@ def initialize(subtopic_id)
end

def solve
if (r = Recommendation.find_by_subtopic_id @subtopic_id)
r.recommended_subtopic_ids
else
sub_topics = get_sub_topics
recommendation = create_recommendation_for sub_topics
recommendation.recommended_subtopic_ids
end
end

private

def get_sub_topics
User.who_listened_to(@subtopic_id).inject({}) do |sub_topics, user|
user.listens.pluck(:subtopic_id).each do |subtopic_id|
next if subtopic_id == @subtopic_id
sub_topics[subtopic_id] ||= 0
sub_topics[subtopic_id] += 1
end
sub_topics
end.sort_by { |subtopic_id, listens_count| listens_count }.reverse
end

def create_recommendation_for(sub_topics)
Recommendation.find_or_create_by(subtopic_id: @subtopic_id).tap do |r|
r.recommended_subtopic_ids = sub_topics.map { |sid, listens_count| [sid, listens_count] }
r.save
end
results = ActiveRecord::Base.connection.execute <<-SQL.strip_heredoc
SELECT subtopic_id, COUNT(id) AS listens_count FROM listens
WHERE listens.user_id IN (
SELECT users.user_id FROM users
RIGHT JOIN listens ON listens.user_id = users.user_id
WHERE listens.subtopic_id = '#{@subtopic_id}'
)
AND listens.subtopic_id != '#{@subtopic_id}'
GROUP BY subtopic_id
ORDER BY listens_count DESC
SQL
results.to_a
end
end
6 changes: 3 additions & 3 deletions app/views/recommendations/index.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
%th SubTopic
%th Listens
%tbody
- @recommendations.each do |subtopic_id, listens|
- sub_topic = SubTopic.find_by_subtopic_id subtopic_id
- @recommendations.each do |recommendation|
- sub_topic = SubTopic.find_by_subtopic_id recommendation["subtopic_id"]
%tr
%td= link_to sub_topic.name, sub_topic
%td= listens
%td= recommendation["listens_count"]

= paginate @recommendations
32 changes: 1 addition & 31 deletions lib/tasks/load.rake
Original file line number Diff line number Diff line change
Expand Up @@ -48,42 +48,12 @@ namespace :load do
puts "\aDone."
end

desc "Generate recommendations from sub_topics"
task recommendations: :environment do
subtopic_ids = SubTopic.pluck :subtopic_id
total = subtopic_ids.size
puts "Creating #{total} recommendations..."

subtopic_ids.each_with_index do |subtopic_id, i|
subtopic = SubTopic.find_by_subtopic_id subtopic_id
puts "\tGenerating recommendations for \"#{subtopic.name}\". #{i+1} of #{total} (#{percent_of i, total}%) done."

Recommender.new(subtopic_id).solve
end
puts "\aDone."
end

desc "Delete and reload SubTopics, Listens, and Users"
task reload: :environment do
puts "Deleting all data..."
[Recommendation, Listen, SubTopic, User].map &:delete_all
[Listen, SubTopic, User].map &:delete_all

puts "Loading all data..."
%w[sub_topics listens users].each { |task| Rake::Task["load:#{task}"].invoke }
end

desc "Clear listens, sub_topics, and users tables"
task reset: :environment do
Rake::Task["load:reload"].invoke
Rake::Task["load:recommendations"].invoke
end

desc "Clear all recommendations"
task clear_recommendations: :environment do
Recommendation.delete_all
end

def percent_of(is, of)
sprintf "%.2f", (is + 1).to_f / of.to_f * 100.0
end
end

0 comments on commit 1af8aa4

Please sign in to comment.