Commit 1be015eb4d8fd5d285e87cae79da7af80114d43b
1 parent
1377962b
Exists in
master
and in
1 other branch
Change catchup to no longer depend on pregenerated prompts
Showing
3 changed files
with
57 additions
and
28 deletions
Show diff stats
app/models/choice.rb
| @@ -54,7 +54,6 @@ class Choice < ActiveRecord::Base | @@ -54,7 +54,6 @@ class Choice < ActiveRecord::Base | ||
| 54 | votes_count || 0 | 54 | votes_count || 0 |
| 55 | end | 55 | end |
| 56 | 56 | ||
| 57 | - after_create :generate_prompts | ||
| 58 | def before_create | 57 | def before_create |
| 59 | #puts "just got inside choice#before_create. is set to active? #{self.active?}" | 58 | #puts "just got inside choice#before_create. is set to active? #{self.active?}" |
| 60 | unless item | 59 | unless item |
app/models/question.rb
| @@ -84,16 +84,16 @@ class Question < ActiveRecord::Base | @@ -84,16 +84,16 @@ class Question < ActiveRecord::Base | ||
| 84 | 84 | ||
| 85 | until prompt && prompt.active? | 85 | until prompt && prompt.active? |
| 86 | target = rand | 86 | target = rand |
| 87 | - prompt_id = nil | 87 | + left_choice_id = right_choice_id = nil |
| 88 | 88 | ||
| 89 | weighted.each do |item, weight| | 89 | weighted.each do |item, weight| |
| 90 | if target <= weight | 90 | if target <= weight |
| 91 | - prompt_id = item | 91 | + left_choice_id, right_choice_id = item.split(", ") |
| 92 | break | 92 | break |
| 93 | end | 93 | end |
| 94 | target -= weight | 94 | target -= weight |
| 95 | end | 95 | end |
| 96 | - prompt = Prompt.find(prompt_id, :include => ['left_choice', 'right_choice']) | 96 | + prompt = prompts.find_or_create_by_left_choice_id_and_right_choice_id(left_choice_id, right_choice_id, :include => [{ :left_choice => :item }, { :right_choice => :item }]) |
| 97 | end | 97 | end |
| 98 | # check if prompt has two active choices here, maybe we can set this on the prompt level too? | 98 | # check if prompt has two active choices here, maybe we can set this on the prompt level too? |
| 99 | prompt | 99 | prompt |
| @@ -104,16 +104,31 @@ class Question < ActiveRecord::Base | @@ -104,16 +104,31 @@ class Question < ActiveRecord::Base | ||
| 104 | def catchup_prompts_weights | 104 | def catchup_prompts_weights |
| 105 | weights = Hash.new(0) | 105 | weights = Hash.new(0) |
| 106 | throttle_min = 0.05 | 106 | throttle_min = 0.05 |
| 107 | - #assuming all prompts exist | 107 | + sum = 0.0 |
| 108 | 108 | ||
| 109 | - #the_prompts = prompts.find(:all, :select => 'id, votes_count') | ||
| 110 | - #We don't really need to instantiate all the objects | ||
| 111 | - the_prompts = ActiveRecord::Base.connection.select_all("SELECT id, votes_count from prompts where question_id =#{self.id}") | 109 | + prompts.find_each(:select => 'votes_count, left_choice_id, right_choice_id') do |p| |
| 110 | + value = [(1.0/ (p.votes.size + 1).to_f).to_f, throttle_min].min | ||
| 111 | + weights["#{p.left_choice_id}, #{p.right_choice_id}"] = value | ||
| 112 | + sum += value | ||
| 113 | + end | ||
| 112 | 114 | ||
| 113 | - the_prompts.each do |p| | ||
| 114 | - weights[p["id"].to_i] = [(1.0/ (p["votes_count"].to_i + 1).to_f).to_f, throttle_min].min | 115 | + # This will not run once all prompts have been generated, |
| 116 | + # but it prevents us from having to pregenerate all possible prompts | ||
| 117 | + if weights.size < choices.size ** 2 - choices.size | ||
| 118 | + choices.each do |l| | ||
| 119 | + choices.each do |r| | ||
| 120 | + if l.id == r.id | ||
| 121 | + next | ||
| 122 | + end | ||
| 123 | + if !weights.has_key?("#{l.id}, #{r.id}") | ||
| 124 | + weights["#{l.id}, #{r.id}"] = throttle_min | ||
| 125 | + sum+=throttle_min | ||
| 126 | + end | ||
| 127 | + end | ||
| 128 | + end | ||
| 115 | end | 129 | end |
| 116 | - normalize!(weights) | 130 | + |
| 131 | + normalize!(weights, sum) | ||
| 117 | weights | 132 | weights |
| 118 | end | 133 | end |
| 119 | 134 | ||
| @@ -148,18 +163,21 @@ class Question < ActiveRecord::Base | @@ -148,18 +163,21 @@ class Question < ActiveRecord::Base | ||
| 148 | 163 | ||
| 149 | end | 164 | end |
| 150 | 165 | ||
| 151 | - def normalize!(weighted) | 166 | + #passing precomputed sum saves us a traversal through the array |
| 167 | + def normalize!(weighted, sum=nil) | ||
| 152 | if weighted.instance_of?(Hash) | 168 | if weighted.instance_of?(Hash) |
| 153 | - sum = weighted.inject(0) do |sum, item_and_weight| | ||
| 154 | - sum += item_and_weight[1] | 169 | + if sum.nil? |
| 170 | + sum = weighted.inject(0) do |sum, item_and_weight| | ||
| 171 | + sum += item_and_weight[1] | ||
| 172 | + end | ||
| 173 | + sum = sum.to_f | ||
| 155 | end | 174 | end |
| 156 | - sum = sum.to_f | ||
| 157 | weighted.each do |item, weight| | 175 | weighted.each do |item, weight| |
| 158 | weighted[item] = weight/sum | 176 | weighted[item] = weight/sum |
| 159 | weighted[item] = 0.0 unless weighted[item].finite? | 177 | weighted[item] = 0.0 unless weighted[item].finite? |
| 160 | end | 178 | end |
| 161 | elsif weighted.instance_of?(Array) | 179 | elsif weighted.instance_of?(Array) |
| 162 | - sum = weighted.inject(0) {|sum, item| sum += item} | 180 | + sum = weighted.inject(0) {|sum, item| sum += item} if sum.nil? |
| 163 | weighted.each_with_index do |item, i| | 181 | weighted.each_with_index do |item, i| |
| 164 | weighted[i] = item/sum | 182 | weighted[i] = item/sum |
| 165 | weighted[i] = 0.0 unless weighted[i].finite? | 183 | weighted[i] = 0.0 unless weighted[i].finite? |
lib/tasks/test_api.rake
| @@ -11,11 +11,14 @@ namespace :test_api do | @@ -11,11 +11,14 @@ namespace :test_api do | ||
| 11 | 11 | ||
| 12 | 12 | ||
| 13 | current_user = User.first | 13 | current_user = User.first |
| 14 | - 3000.times do | ||
| 15 | - question = Question.find(120) # test question change as needed | ||
| 16 | - @p = Prompt.find(question.catchup_choose_prompt_id) | ||
| 17 | - | ||
| 18 | - current_user.record_vote("test_vote", @p, rand(2)) | 14 | + 1000.times do |n| |
| 15 | + puts "#{n} votes completed" if n % 100 == 0 | ||
| 16 | + question = Question.find(214) # test question change as needed | ||
| 17 | + @prompt = question.catchup_choose_prompt | ||
| 18 | + @appearance = current_user.record_appearance(current_user.default_visitor, @prompt) | ||
| 19 | + | ||
| 20 | + direction = (rand(2) == 0) ? "left" : "right" | ||
| 21 | + current_user.record_vote(:prompt => @prompt, :direction => direction, :appearance_lookup => @appearance.lookup) | ||
| 19 | end | 22 | end |
| 20 | 23 | ||
| 21 | end | 24 | end |
| @@ -183,20 +186,29 @@ namespace :test_api do | @@ -183,20 +186,29 @@ namespace :test_api do | ||
| 183 | desc "Dump votes of a question by left vs right id" | 186 | desc "Dump votes of a question by left vs right id" |
| 184 | task(:make_csv => :environment) do | 187 | task(:make_csv => :environment) do |
| 185 | 188 | ||
| 186 | - q = Question.find(120) | 189 | + q = Question.find(214) |
| 190 | + | ||
| 187 | 191 | ||
| 188 | the_prompts = q.prompts_hash_by_choice_ids | 192 | the_prompts = q.prompts_hash_by_choice_ids |
| 189 | 193 | ||
| 190 | #hash_of_choice_ids_from_left_to_right_to_votes | 194 | #hash_of_choice_ids_from_left_to_right_to_votes |
| 191 | the_hash = {} | 195 | the_hash = {} |
| 192 | - the_prompts.each do |key, p| | ||
| 193 | - left_id, right_id = key.split(", ") | ||
| 194 | - if not the_hash.has_key?(left_id) | ||
| 195 | - the_hash[left_id] = {} | ||
| 196 | - the_hash[left_id][left_id] = 0 | 196 | + q.choices.each do |l| |
| 197 | + q.choices.each do |r| | ||
| 198 | + next if l.id == r.id | ||
| 199 | + | ||
| 200 | + if not the_hash.has_key?(l.id) | ||
| 201 | + the_hash[l.id] = {} | ||
| 202 | + the_hash[l.id][l.id] = 0 | ||
| 197 | end | 203 | end |
| 198 | 204 | ||
| 199 | - the_hash[left_id][right_id] = p.votes.size | 205 | + p = the_prompts["#{l.id}, #{r.id}"] |
| 206 | + if p.nil? | ||
| 207 | + the_hash[l.id][r.id] = 0 | ||
| 208 | + else | ||
| 209 | + the_hash[l.id][r.id] = p.appearances.size | ||
| 210 | + end | ||
| 211 | + end | ||
| 200 | end | 212 | end |
| 201 | 213 | ||
| 202 | the_hash.sort.each do |xval, row| | 214 | the_hash.sort.each do |xval, row| |