Class: Meekster::Round
- Inherits:
-
Object
- Object
- Meekster::Round
- Defined in:
- lib/meekster/round.rb
Instance Attribute Summary collapse
-
#ballots ⇒ Object
Returns the value of attribute ballots.
-
#candidates ⇒ Object
Returns the value of attribute candidates.
-
#omega ⇒ Object
Returns the value of attribute omega.
-
#quota ⇒ Object
Returns the value of attribute quota.
-
#seats ⇒ Object
Returns the value of attribute seats.
-
#surplus ⇒ Object
Returns the value of attribute surplus.
Class Method Summary collapse
Instance Method Summary collapse
- #calculate_total_surplus! ⇒ Object
- #candidate_elected_this_round? ⇒ Boolean
- #count_complete? ⇒ Boolean
- #defeat_low_candidate! ⇒ Object
- #distribute_votes! ⇒ Object
- #find_winners! ⇒ Object
-
#initialize(parameters = {}) ⇒ Round
constructor
A new instance of Round.
-
#log(msg) ⇒ Object
DEBUGGING.
- #need_to_defeat_low_candidate? ⇒ Boolean
- #reset_votes! ⇒ Object
- #run! ⇒ Object
- #tiebreaker_select(array) ⇒ Object
- #update_keep_factors! ⇒ Object
- #update_quota! ⇒ Object
Constructor Details
#initialize(parameters = {}) ⇒ Round
Returns a new instance of Round.
6 7 8 9 10 11 12 13 |
# File 'lib/meekster/round.rb', line 6 def initialize(parameters={}) self.ballots = parameters[:ballots] self.candidates = parameters[:candidates] self.seats = parameters[:seats] self.omega = parameters[:omega] @candidate_elected_this_round = false end |
Instance Attribute Details
#ballots ⇒ Object
Returns the value of attribute ballots.
4 5 6 |
# File 'lib/meekster/round.rb', line 4 def ballots @ballots end |
#candidates ⇒ Object
Returns the value of attribute candidates.
4 5 6 |
# File 'lib/meekster/round.rb', line 4 def candidates @candidates end |
#omega ⇒ Object
Returns the value of attribute omega.
4 5 6 |
# File 'lib/meekster/round.rb', line 4 def omega @omega end |
#quota ⇒ Object
Returns the value of attribute quota.
4 5 6 |
# File 'lib/meekster/round.rb', line 4 def quota @quota end |
#seats ⇒ Object
Returns the value of attribute seats.
4 5 6 |
# File 'lib/meekster/round.rb', line 4 def seats @seats end |
#surplus ⇒ Object
Returns the value of attribute surplus.
4 5 6 |
# File 'lib/meekster/round.rb', line 4 def surplus @surplus end |
Class Method Details
.round_up_to_nine_decimal_places(x) ⇒ Object
192 193 194 |
# File 'lib/meekster/round.rb', line 192 def self.round_up_to_nine_decimal_places(x) ((x * BigDecimal('1E9')).ceil)/BigDecimal('1E9') end |
.truncate_to_nine_decimal_places(x) ⇒ Object
196 197 198 |
# File 'lib/meekster/round.rb', line 196 def self.truncate_to_nine_decimal_places(x) ((x * BigDecimal('1E9')).floor)/BigDecimal('1E9') end |
Instance Method Details
#calculate_total_surplus! ⇒ Object
118 119 120 121 122 123 124 125 126 127 128 129 |
# File 'lib/meekster/round.rb', line 118 def calculate_total_surplus! elected_candidates = candidates.select{|c| c.state == :elected} sum_of_surpluses = elected_candidates.inject(BigDecimal('0')){|memo, c| memo + (c.votes - quota)} log "Calculating total surplus. Sum of surpluses: #{sum_of_surpluses.to_f}" if sum_of_surpluses < 0 self.surplus = 0 @previous_surpluses.push(surplus) else self.surplus = sum_of_surpluses @previous_surpluses.push(surplus) end end |
#candidate_elected_this_round? ⇒ Boolean
131 132 133 |
# File 'lib/meekster/round.rb', line 131 def candidate_elected_this_round? !!@candidate_elected_this_round end |
#count_complete? ⇒ Boolean
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/meekster/round.rb', line 57 def count_complete? # Are all seats filled? elected_candidates_count = candidates.select{|c| c.state == :elected}.length if elected_candidates_count >= seats log "Count complete: enough elected candidates to fill the seats" return true end # Is number of elected plus hopeful candidates less than or equal to number of seats? hopeful_candidates_count = candidates.select{|c| c.state == :hopeful}.length if (elected_candidates_count + hopeful_candidates_count) <= seats log "Count complete: elected+hopeful candidates less than or equal to seats" return true end false end |
#defeat_low_candidate! ⇒ Object
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/meekster/round.rb', line 150 def defeat_low_candidate! log "Defeating lowest candidate:" hopeful_candidates = candidates.select{|c| c.state == :hopeful} candidate_with_lowest_vote = nil hopeful_candidates.each do |hopeful_candidate| if candidate_with_lowest_vote.nil? || hopeful_candidate.votes < candidate_with_lowest_vote.votes candidate_with_lowest_vote = hopeful_candidate end end log " Lowest candidate: #{candidate_with_lowest_vote.name}" # Detect ties hopeful_candidates.delete(candidate_with_lowest_vote) tied_candidates = hopeful_candidates.select{|c| c.votes <= (candidate_with_lowest_vote.votes + surplus)} if tied_candidates.empty? candidate_to_defeat = candidate_with_lowest_vote else candidates_with_lowest_votes = tied_candidates candidates_with_lowest_votes.push(candidate_with_lowest_vote) candidate_to_defeat = tiebreaker_select(candidates_with_lowest_votes) end candidate_to_defeat.state = :defeated candidate_to_defeat.keep_factor = BigDecimal.new('0') end |
#distribute_votes! ⇒ Object
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/meekster/round.rb', line 81 def distribute_votes! ballots.each do |ballot| ballot.weight = BigDecimal('1') ballot.ranking.each do |ranked_candidate| weight_times_keep_factor = ballot.weight * ranked_candidate.keep_factor weight_times_keep_factor = Meekster::Round.round_up_to_nine_decimal_places(weight_times_keep_factor) ranked_candidate.votes += weight_times_keep_factor ballot.weight -= weight_times_keep_factor break if ballot.weight <= 0 end end end |
#find_winners! ⇒ Object
108 109 110 111 112 113 114 115 116 |
# File 'lib/meekster/round.rb', line 108 def find_winners! candidates.select{|c| c.state == :hopeful}.each do |candidate| if candidate.votes >= quota log "Found winner: #{candidate.name}" candidate.state = :elected @candidate_elected_this_round = true end end end |
#log(msg) ⇒ Object
DEBUGGING
206 207 208 209 210 |
# File 'lib/meekster/round.rb', line 206 def log(msg) return # Comment out to enable debugging puts msg gets end |
#need_to_defeat_low_candidate? ⇒ Boolean
135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/meekster/round.rb', line 135 def need_to_defeat_low_candidate? if surplus < omega log "Need to defeat low candidate." return true end if @previous_surpluses.length > 1 && (surplus >= @previous_surpluses[-2]) log "Need to defeat low candidate (surplus greater than previous iteration)." return true end log "Do not need to defeat low candidate." false end |
#reset_votes! ⇒ Object
75 76 77 78 79 |
# File 'lib/meekster/round.rb', line 75 def reset_votes! candidates.each do |candidate| candidate.votes = BigDecimal('0') end end |
#run! ⇒ Object
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/meekster/round.rb', line 15 def run! if count_complete? return # actually return something? end @previous_surpluses = [] while true do log "STARTING NEW ITERATION" log "Keep factors:" candidates.each do |candidate| log " #{candidate.name} | #{candidate.keep_factor.to_f}" end reset_votes! distribute_votes! log "Counted votes:" candidates.each do |candidate| log " #{candidate.name} | #{candidate.votes.to_f}" end update_quota! find_winners! calculate_total_surplus! if candidate_elected_this_round? return end if need_to_defeat_low_candidate? defeat_low_candidate! return end update_keep_factors! end end |
#tiebreaker_select(array) ⇒ Object
200 201 202 |
# File 'lib/meekster/round.rb', line 200 def tiebreaker_select(array) array[rand(array.length)] end |
#update_keep_factors! ⇒ Object
179 180 181 182 183 184 185 186 187 188 189 190 |
# File 'lib/meekster/round.rb', line 179 def update_keep_factors! log "Updating keep factors:" elected_candidates = candidates.select{|c| c.state == :elected} quota_rounded = Meekster::Round.round_up_to_nine_decimal_places(quota) elected_candidates.each do |elected_candidate| elected_candidate_votes_rounded = Meekster::Round.round_up_to_nine_decimal_places(elected_candidate.votes) new_keep_factor = elected_candidate.keep_factor * quota_rounded new_keep_factor = new_keep_factor / elected_candidate_votes_rounded log " Candidate #{elected_candidate.name}: #{elected_candidate.keep_factor.to_f} -> #{new_keep_factor.to_f}" elected_candidate.keep_factor = new_keep_factor end end |
#update_quota! ⇒ Object
99 100 101 102 103 104 105 106 |
# File 'lib/meekster/round.rb', line 99 def update_quota! sum_of_votes = candidates.inject(BigDecimal('0')){|sum, c| sum + c.votes} new_quota = sum_of_votes / (seats + 1) # TODO truncate to nine decimal places new_quota += BigDecimal('0.000000001') log "Updating quota: #{new_quota.to_f}" self.quota = new_quota end |