1 # election library -- a ruby library for elections
2 # copyright © 2005 MIT Media Lab and Benjamin Mako Hill
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 2 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 #################################################################
20 ## ==== condorcet.rb ====
22 ## This file contains Condorcet election methods. Currently this
23 ## includes a pure condorcet and a CloneproofSSD implementation
24 ## modeled after the Python-based Debian project election code and
25 ## that gives the same results in several tested corner cases.
26 #################################################################
28 ##################################################################
29 ## CondorcetVote Classes and SubClasses
31 ## The CondorcetVote class is subclassed by the PureCondorcetVote and
32 ## the CloneproofSSDVote classes but should not be used directly.
34 class CondorcetVote < ElectionVote
36 attr_accessor :results
38 def initialize(votes=nil)
39 unless defined?(@candidates)
40 @candidates = Array.new
41 votes.each do |vote_row|
42 vote_row = vote_row.flatten if vote_row.class == Array
43 vote_row.each do |vote|
44 @candidates << vote unless @candidates.include?(vote)
52 def tally_vote(vote=nil)
54 vote.each_with_index do |winners, index|
55 if vote.flatten.length < @candidates.length
56 implied_losers = @candidates.select { |c| not vote.flatten.include?(c) }
57 vote.push(implied_losers)
59 if vote.length - 1 == index
62 losers = vote.flatten.last( vote.flatten.length - index - 1)
65 losers.each do |place|
66 place = [place] unless place.class == Array
69 winners = [winners] unless winners.class == Array
70 next if winners.include?(loser)
71 winners.each do |winner|
72 @votes[winner] = Hash.new unless @votes.has_key?(winner)
73 @votes[loser] = Hash.new unless @votes.has_key?(loser)
75 if @votes[winner].has_key?(loser)
76 @votes[winner][loser] += 1
78 @votes[winner][loser] = 1
81 # make sure we have a comparable object
82 @votes[loser][winner] = 0 unless @votes[loser].has_key?( winner )
90 if @results.size < 2 && (not @candidates.empty?)
97 find_only_winner unless @winner
103 def verify_vote(vote=nil)
104 vote.instance_of?( Array ) and
109 find_only_winner unless @winner
110 until @candidates.empty?
111 aResult = resultFactory( self )
112 @results << aResult.winners
118 @winner = resultFactory( self )
119 @results << @winner.winners
125 class PureCondorcetVote < CondorcetVote
126 def resultFactory(init)
127 PureCondorcetResult.new(init)
131 class CloneproofSSDVote < CondorcetVote
132 def resultFactory(init)
133 CloneproofSSDResult.new(init)
139 ##################################################################
140 ## CondorcetResult Classes and SubClasses
142 ## The CondorcetResult class is subclassed by the PureCondorcetResult
143 ## and the CloneproofSSDResult classes but should not be used
146 class CondorcetResult < ElectionResult
147 def initialize(voteobj=nil)
148 unless voteobj and voteobj.kind_of?( CondorcetVote )
149 raise ArgumentError, "You must pass a CondorcetVote array.", caller
156 def defeats(candidates=nil, votes=nil)
157 candidates = @election.candidates unless candidates
158 votes = @election.votes unless votes
161 candidates = [candidates] unless candidates.class == Array
162 candidates.each do |candidate|
163 candidates.each do |challenger|
164 next if candidate == challenger
165 if votes[candidate][challenger] > votes[challenger][candidate]
166 defeats << [ candidate, challenger ]
176 class PureCondorcetResult < CondorcetResult
177 def initialize(voteobj=nil)
185 votes = @election.votes
186 candidates = @election.candidates
189 candidates.each do |candidate|
190 victors[candidate] = Array.new
193 self.defeats.each do |pair|
194 winner, loser = *pair
195 victors[winner] << loser
200 winners = @election.candidates.find_all do |candidate|
201 victors[candidate].length == @election.candidates.length - victory_margin
203 if winners.length > 0
213 class CloneproofSSDResult < CondorcetResult
214 def initialize(voteobj=nil)
216 @winners = self.cpssd()
222 votes = @election.votes
223 candidates = *@election.candidates
225 def in_schwartz_set?(candidate, candidates, transitive_defeats)
226 candidates.each do |challenger|
227 next if candidate == challenger
229 if transitive_defeats.include?( [ challenger, candidate ] ) and
230 not transitive_defeats.include?( [ candidate, challenger ] )
239 # see the array with the standard defeats
240 transitive_defeats = self.defeats(candidates, votes)
241 defeats_hash = Hash.new
242 transitive_defeats.each { |td| defeats_hash[td] = 1 }
244 candidates = [candidates] unless candidates.class == Array
245 candidates.each do |cand1|
246 candidates.each do |cand2|
247 unless cand1 == cand2
248 candidates.each do |cand3|
249 if not cand2 == cand3 and
250 not cand1 == cand3 and
251 defeats_hash[[cand2, cand1]] and
252 defeats_hash[[cand1, cand3]] and
253 not defeats_hash[[cand2, cand3]]
254 transitive_defeats << [cand2, cand3]
255 defeats_hash[[cand2, cand3]] = 1
262 schwartz_set = Array.new
263 candidates.each do |candidate|
264 if in_schwartz_set?(candidate, candidates, transitive_defeats)
265 schwartz_set << candidate
269 candidates = schwartz_set
271 # look through the schwartz set now for defeats
272 defeats = self.defeats(candidates, votes)
274 # it's a tie or there's only one option
275 break if defeats.length == 0
277 def is_weaker_defeat?( pair1, pair2 )
278 votes = @election.votes
279 if votes[pair1[0]][pair1[1]] > votes[pair2[0]][pair2[1]]
281 elsif votes[pair1[0]][pair1[1]] == votes[pair2[0]][pair2[1]] and
282 votes[pair1[1]][pair1[0]] > votes[pair2[1]][pair2[0]]
289 defeats.sort! do |pair1, pair2|
290 if is_weaker_defeat?( pair1, pair2 )
292 elsif is_weaker_defeat?( pair2, pair1 )
299 votes[defeats[0][0]][defeats[0][1]] = 0
300 votes[defeats[0][1]][defeats[0][0]] = 0