1 # IRV is a preferential voting system used widely for government elections
2 # in Australia and New Zealand and elsewhere. IRV asks voters to rank
3 # candidates in preference and then holds a series of "runoff" elections
4 # by eliminating the weakest candidate and recomputing the election
5 # results until there exists a candidate who has a majority of the
11 # vote_array = [ ["A", "B"], ["B", "A"], ["B", "A"] ]
12 # resultobject = InstantRunoffVote.new(vote_array).result
14 class InstantRunoffVote < ElectionVote
16 def initialize(votes=nil)
17 @candidates = Array.new
19 @candidates = vote.uniq if vote.uniq.length > candidates.length
22 @candidates.each do |candidate|
23 @votes[candidate] = [0, Hash.new] unless @votes.has_key?(candidate)
28 InstantRunoffResult.new(self, params)
32 def vote_valid?(vote = nil)
33 super && vote.is_a?(Array) && vote == vote.uniq
38 candidate = votecopy.shift
39 votes[candidate] = [0, Hash.new] unless votes.has_key?(candidate)
40 votes[candidate][0] += 1
41 if votes[candidate][1].has_key?(votecopy)
42 votes[candidate][1][votecopy] += 1
44 votes[candidate][1][votecopy] = 1
50 class InstantRunoffLogicVote < InstantRunoffVote
52 InstantRunoffLogicResult.new(self, params)
56 class InstantRunoffFirstRoundVote < InstantRunoffVote
58 InstantRunoffFirstRoundResult.new(self, params)
62 class InstantRunoffAllVote < InstantRunoffVote
64 InstantRunoffAllResult.new(self, params)
68 class InstantRunoffRandomVote < InstantRunoffVote
70 InstantRunoffRandomResult.new(self, params)
74 class InstantRunoffResult < ElectionResult
75 attr_reader :ranked_candidates
77 def initialize(voteobj=nil, params={})
78 unless voteobj and voteobj.kind_of?( InstantRunoffVote )
79 raise ArgumentError, "You must pass an InstantRunoffVote array.", caller
83 votes = @election.votes.clone
84 candidates = @election.candidates
85 votes_sum = votes.inject(0) {|n, value| n + value[1][0]}
86 @majority = votes_sum/2 + 1
87 @ranked_candidates = Array.new()
88 ranked_candidates = Array.new()
91 if params.has_key?('candidate_count')
92 apply_candidate_count(votes, params['candidate_count'])
94 if params.has_key?('vote_minimum')
95 apply_vote_minimum(votes, params['vote_minimum'])
97 if params.has_key?('percent_minimum')
98 apply_vote_minimum(votes, votes_sum * params['percent_minimum'])
100 if params.has_key?('percent_retention')
101 apply_retention(votes, votes_sum * params['percent_retention'])
104 unless votes.length > 0
110 ranked_candidates = votes.sort do |a, b|
112 end.collect {|i| i[0]}
113 @winners = ranked_candidates.find_all do |i|
114 votes[i][0] >= @majority
116 end while not self.winner? and next_round(votes, ranked_candidates)
117 @ranked_candidates.unshift(*ranked_candidates)
121 def apply_candidate_count(votes, candidate_count)
122 if votes.size > candidate_count
123 losers = votes.sort do |a, b|
125 end.collect {|i| i[0]}.last(votes.size - candidate_count)
126 @ranked_candidates.unshift(losers) unless losers.empty?
127 losers.each { |loser| remove_candidate(votes, loser) }
131 def apply_vote_minimum(votes, vote_minimum)
132 losers = votes.find_all do |i|
133 i[1][0] < vote_minimum
134 end.collect {|i| i[0]}
135 if losers.length == votes.size
138 @ranked_candidates.unshift(losers) unless losers.empty?
139 losers.each { |loser| remove_candidate(votes, loser) }
143 def apply_retention(votes, retention)
144 losers = votes.sort do |a, b|
146 end.collect {|i| i[0]}
148 while partial_sum < retention
149 partial_sum += votes[losers.shift][0]
151 @ranked_candidates.unshift(losers) unless losers.empty?
152 losers.each { |loser| remove_candidate(votes, loser) }
155 def next_round(votes, ranked_candidates)
156 loser = ranked_candidates[-1]
157 if votes.empty? or votes[loser][0] == votes[ranked_candidates[-2]][0]
160 @ranked_candidates.unshift(loser)
161 remove_candidate(votes, loser)
166 def remove_candidate(votes, loser)
167 votes.each_pair do |candidate, morevotes|
169 hash.each_pair do |vote, count|
175 votes[loser][1].each_pair do |vote, count|
176 candidate = vote.shift
177 votes[candidate][0] += count
178 if votes[candidate][1].has_key?(vote)
179 votes[candidate][1][vote] += count
181 votes[candidate][1][vote] = count
188 class InstantRunoffLogicResult < InstantRunoffResult
189 def next_round(votes, ranked_candidates)
190 losers = ranked_candidates.find_all do |i|
191 votes[i][0] == votes[ranked_candidates[-1]][0]
193 if losers.inject(0) {|n, loser| n + votes[loser][0]} >= @majority
196 @ranked_candidates.unshift(losers)
197 losers.each do |loser|
198 remove_candidate(votes, loser)
205 class InstantRunoffFirstRoundResult < InstantRunoffResult
206 def next_round(votes, ranked_candidates)
207 losers = ranked_candidates.find_all do |i|
208 votes[i][0] == votes[ranked_candidates[-1]][0]
210 loser = losers.sort do |a, b|
211 @election.votes[a][0] <=> @election.votes[b][0]
213 @ranked_candidates.unshift(loser)
214 remove_candidate(votes, loser)
218 class InstantRunoffAllResult < InstantRunoffResult
219 def next_round(votes, ranked_candidates)
220 losers = ranked_candidates.find_all do |i|
221 votes[i][0] == votes[ranked_candidates[-1]][0]
223 if losers.length == ranked_candidates.length
226 @ranked_candidates.unshift(losers)
227 losers.each do |loser|
228 remove_candidate(votes, loser)
235 class InstantRunoffRandomResult < InstantRunoffResult
236 def next_round(votes, ranked_candidates)
237 losers = ranked_candidates.find_all do |i|
238 votes[i][0] == votes[ranked_candidates[-1]][0]
240 loser = losers[rand(losers.length)]
241 @ranked_candidates.unshift(loser)
242 remove_candidate(votes, loser)