Commit 387cad08f262945185d4c9de181b177bfe42cb7d

Authored by Dhruv Kapadia
1 parent fdc546eb

Catchup algorithm

app/models/choice.rb
... ... @@ -44,7 +44,7 @@ class Choice < ActiveRecord::Base
44 44 votes_count || 0
45 45 end
46 46  
47   - #after_create :generate_prompts
  47 + after_create :generate_prompts
48 48 def before_create
49 49 puts "just got inside choice#before_create. is set to active? #{self.active?}"
50 50 unless item
... ... @@ -90,9 +90,31 @@ class Choice < ActiveRecord::Base
90 90 #do this in a new process (via delayed jobs)
91 91 previous_choices = (self.question.choices - [self])
92 92 return if previous_choices.empty?
93   - previous_choices.each { |c|
94   - question.prompts.create!(:left_choice => c, :right_choice => self)
95   - question.prompts.create!(:left_choice => self, :right_choice => c)
96   - }
  93 + inserts = []
  94 +
  95 + timestring = Time.now.to_s(:db) #isn't rails awesome?
  96 +
  97 + #add prompts with this choice on the left
  98 + previous_choices.each do |r|
  99 + inserts.push("(NULL, #{self.question_id}, NULL, #{self.id}, '#{timestring}', '#{timestring}', NULL, 0, #{r.id}, NULL, NULL)")
  100 + end
  101 + #add prompts with this choice on the right
  102 + previous_choices.each do |l|
  103 + inserts.push("(NULL, #{self.question_id}, NULL, #{l.id}, '#{timestring}', '#{timestring}', NULL, 0, #{self.id}, NULL, NULL)")
  104 + end
  105 + sql = "INSERT INTO `prompts` (`algorithm_id`, `question_id`, `voter_id`, `left_choice_id`, `created_at`, `updated_at`, `tracking`, `votes_count`, `right_choice_id`, `active`, `randomkey`) VALUES #{inserts.join(', ')}"
  106 +
  107 + Question.update_counters(self.question_id, :prompts_count => 2*previous_choices.size)
  108 +
  109 + logger.info("The sql is:::: #{sql}")
  110 +
  111 + ActiveRecord::Base.connection.execute(sql)
  112 +
  113 +#VALUES (NULL, 108, NULL, 1892, '2010-03-16 11:12:37', '2010-03-16 11:12:37', NULL, 0, 1893, NULL, NULL)
  114 +# INSERT INTO `prompts` (`algorithm_id`, `question_id`, `voter_id`, `left_choice_id`, `created_at`, `updated_at`, `tracking`, `votes_count`, `right_choice_id`, `active`, `randomkey`) VALUES(NULL, 108, NULL, 1892, '2010-03-16 11:12:37', '2010-03-16 11:12:37', NULL, 0, 1893, NULL, NULL)
  115 + #previous_choices.each { |c|
  116 + # question.prompts.create!(:left_choice => c, :right_choice => self)
  117 + # question.prompts.create!(:left_choice => self, :right_choice => c)
  118 + #}
97 119 end
98 120 end
... ...
app/models/prompt.rb
... ... @@ -38,14 +38,6 @@ class Prompt < ActiveRecord::Base
38 38 left_choice.item.data
39 39 end
40 40  
41   - def left_choice_id
42   - left_choice.id
43   - end
44   -
45   - def right_choice_id
46   - right_choice.id
47   - end
48   -
49 41 def active?
50 42 left_choice.active? and right_choice.active?
51 43 end
... ...
app/models/question.rb
... ... @@ -37,6 +37,44 @@ class Question < ActiveRecord::Base
37 37 end until @p.active?
38 38 return @p
39 39 end
  40 +
  41 + # adapted from ruby cookbook(2006): section 5-11
  42 + def catchup_choose_prompt_id
  43 + weighted = catchup_prompts_weights
  44 + # Rand returns a number from 0 - 1, so weighted needs to be normalized
  45 + target = rand
  46 + weighted.each do |item, weight|
  47 + return item if target <= weight
  48 + target -= weight
  49 + end
  50 + # check if prompt has two active choices here, maybe we can set this on the prompt level too?
  51 + end
  52 +
  53 +
  54 + # TODO Add index for question id on prompts table
  55 + def catchup_prompts_weights
  56 + weights = Hash.new(0)
  57 + throttle_min = 0.05
  58 + #assuming all prompts exist
  59 + prompts.each do |p|
  60 + weights[p.id] = [(1.0/ (p.votes.size + 1).to_f).to_f, throttle_min].min
  61 + end
  62 + normalize!(weights)
  63 + weights
  64 + end
  65 +
  66 + def normalize!(weighted)
  67 + if weighted.instance_of?(Hash)
  68 + sum = weighted.inject(0) do |sum, item_and_weight|
  69 + sum += item_and_weight[1]
  70 + end
  71 + sum = sum.to_f
  72 + weighted.each { |item, weight| weighted[item] = weight/sum }
  73 + elsif weighted.instance_of?(Array)
  74 + sum = weighted.inject(0) {|sum, item| sum += item}
  75 + weighted.each_with_index {|item, i| weighted[i] = item/sum}
  76 + end
  77 + end
40 78  
41 79 def distinct_array_of_choice_ids(rank = 2, only_active = true)
42 80 @choice_ids = choice_ids
... ...
db/migrate/20100317161212_add_question_index_to_prompts.rb 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +class AddQuestionIndexToPrompts < ActiveRecord::Migration
  2 + def self.up
  3 + add_index :prompts, :question_id
  4 + end
  5 +
  6 + def self.down
  7 + remove_index :prompts, :question_id
  8 + end
  9 +end
... ...
lib/tasks/test_api.rake
  1 +require 'fastercsv'
1 2 namespace :test_api do
2 3  
3 4 task :all => [:question_vote_consistency]
4 5  
  6 + desc "Don't run unless you know what you are doing"
  7 + task(:generate_lots_of_votes => :environment) do
  8 + if Rails.env.production?
  9 + print "You probably don't want to run this in production as it will falsify a bunch of random votes"
  10 + end
  11 +
  12 +
  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))
  19 + end
  20 +
  21 + end
  22 +
  23 + desc "Should only need to be run once"
  24 + task(:generate_all_possible_prompts => :environment) do
  25 + inserts = []
  26 + Question.find(:all).each do |q|
  27 + choices = q.choices
  28 + if q.prompts.size > choices.size**2 - choices.size
  29 + print "ERROR: #{q.id}\n"
  30 + next
  31 + elsif q.prompts.size == choices.size**2 - choices.size
  32 + print "#{q.id} has enough prompts, skipping...\n"
  33 + next
  34 + else
  35 + print "#{q.id} should add #{(choices.size ** 2 - choices.size) - q.prompts.size}\n"
  36 +
  37 + end
  38 + timestring = Time.now.to_s(:db) #isn't rails awesome?
  39 + promptscount=0
  40 + the_prompts = Prompt.find(:all, :select => 'id, left_choice_id, right_choice_id', :conditions => {:question_id => q.id})
  41 +
  42 + the_prompts_hash = {}
  43 + the_prompts.each do |p|
  44 + the_prompts_hash["#{p.left_choice_id},#{p.right_choice_id}"] = 1
  45 + end
  46 +
  47 + choices.each do |l|
  48 + choices.each do |r|
  49 + if l.id == r.id
  50 + next
  51 + else
  52 + #p = the_prompts.find{|o| o.left_choice_id == l.id && o.right_choice_id == r.id}
  53 + keystring = "#{l.id},#{r.id}"
  54 + p = the_prompts_hash[keystring]
  55 + if p.nil?
  56 + print "."
  57 + inserts.push("(NULL, #{q.id}, NULL, #{l.id}, '#{timestring}', '#{timestring}', NULL, 0, #{r.id}, NULL, NULL)")
  58 + promptscount+=1
  59 + end
  60 +
  61 + end
  62 +
  63 + end
  64 + end
  65 +
  66 + print "Added #{promptscount} to #{q.id}\n"
  67 +
  68 + Question.update_counters(q.id, :prompts_count => promptscount)
  69 +
  70 + end
  71 +
  72 + sql = "INSERT INTO `prompts` (`algorithm_id`, `question_id`, `voter_id`, `left_choice_id`, `created_at`, `updated_at`, `tracking`, `votes_count`, `right_choice_id`, `active`, `randomkey`) VALUES #{inserts.join(', ')}"
  73 +
  74 + unless inserts.empty?
  75 + ActiveRecord::Base.connection.execute(sql)
  76 + end
  77 +
  78 + end
  79 +
  80 +
  81 + desc "Dump votes of a question by left vs right id"
  82 + task(:make_csv => :environment) do
  83 +
  84 + q = Question.find(120)
  85 +
  86 + the_prompts = q.prompts_hash_by_choice_ids
  87 +
  88 + #hash_of_choice_ids_from_left_to_right_to_votes
  89 + the_hash = {}
  90 + the_prompts.each do |key, p|
  91 + left_id, right_id = key.split(", ")
  92 + if not the_hash.has_key?(left_id)
  93 + the_hash[left_id] = {}
  94 + the_hash[left_id][left_id] = 0
  95 + end
  96 +
  97 + the_hash[left_id][right_id] = p.votes.size
  98 + end
  99 +
  100 + the_hash.sort.each do |xval, row|
  101 + rowarray = []
  102 + row.sort.each do |yval, cell|
  103 + rowarray << cell
  104 + end
  105 + puts rowarray.join(", ")
  106 + end
  107 + end
  108 +
  109 +
5 110 desc "Description here"
6 111 task(:question_vote_consistency => :environment) do
7 112 questions = Question.find(:all)
... ...