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 def tally_vote(vote=nil)
38 vote.each_with_index do |winner, index|
39 # only run through this if this *is* preferred to something
40 break if vote.length - 1 == index
41 losers = vote.last( vote.length - index )
43 losers.each do |loser|
44 next if winner == loser
46 @votes[winner] = Hash.new unless @votes.has_key?(winner)
47 @votes[loser] = Hash.new unless @votes.has_key?(loser)
49 if @votes[winner].has_key?(loser)
50 @votes[winner][loser] += 1
52 @votes[winner][loser] = 1
55 # make sure we have a comparable object
56 @votes[loser][winner] = 0 unless @votes[loser].has_key?( winner )
58 @candidates << loser unless @candidates.include?( loser )
61 @candidates << winner unless @candidates.include?( winner )
66 top_result = resultFactory( self )
67 until @candidates.empty?
68 aResult = resultFactory( self )
69 top_result.full_results << aResult
76 def verify_vote(vote=nil)
77 vote.instance_of?( Array ) and
83 class PureCondorcetVote < CondorcetVote
84 def resultFactory(init)
85 PureCondorcetResult.new(init)
89 class CloneproofSSDVote < CondorcetVote
90 def resultFactory(init)
91 CloneproofSSDResult.new(init)
96 ##################################################################
97 ## CondorcetResult Classes and SubClasses
99 ## The CondorcetResult class is subclassed by the PureCondorcetResult
100 ## and the CloneproofSSDResult classes but should not be used
103 class CondorcetResult < ElectionResult
104 def initialize(voteobj=nil)
105 unless voteobj and voteobj.kind_of?( CondorcetVote )
106 raise ArgumentError, "You must pass a CondorcetVote array.", caller
112 def defeats(candidates=nil, votes=nil)
113 candidates = @election.candidates unless candidates
114 votes = @election.votes unless votes
117 candidates.each do |candidate|
118 candidates.each do |challenger|
119 next if candidate == challenger
120 if votes[candidate][challenger] > votes[challenger][candidate]
121 defeats << [ candidate, challenger ]
131 class PureCondorcetResult < CondorcetResult
132 def initialize(voteobj=nil)
139 votes = @election.votes
140 candidates = @election.candidates
143 candidates.each do |candidate|
144 victors[candidate] = Array.new
147 self.defeats.each do |pair|
148 winner, loser = *pair
149 victors[winner] << loser
152 winners = @election.candidates.find_all do |candidate|
153 victors[candidate].length == @election.candidates.length - 1
156 @winners << winners if winners.length == 1
160 class CloneproofSSDResult < CondorcetResult
161 def initialize(voteobj=nil)
163 @winners = self.cpssd()
168 votes = @election.votes
169 candidates = *@election.candidates
171 def in_schwartz_set?(candidate, candidates, transitive_defeats)
172 candidates.each do |challenger|
173 next if candidate == challenger
175 if transitive_defeats.include?( [ challenger, candidate ] ) and
176 not transitive_defeats.include?( [ candidate, challenger ] )
185 # see the array with the standard defeats
186 transitive_defeats = self.defeats(candidates, votes)
188 candidates.each do |cand1|
189 candidates.each do |cand2|
190 candidates.each do |cand3|
191 if transitive_defeats.include?( [ cand2, cand1 ] ) and
192 transitive_defeats.include?( [ cand1, cand3 ] ) and
193 not transitive_defeats.include?( [ cand2, cand3 ] ) and
195 transitive_defeats << [ cand2, cand3 ]
201 schwartz_set = Array.new
202 candidates.each do |candidate|
203 if in_schwartz_set?(candidate, candidates, transitive_defeats)
204 schwartz_set << candidate
208 candidates = schwartz_set
210 # look through the schwartz set now for defeats
211 defeats = self.defeats(candidates, votes)
213 # it's a tie or there's only one option
214 break if defeats.length == 0
216 def is_weaker_defeat?( pair1, pair2 )
217 votes = @election.votes
218 if votes[pair1[0]][pair1[1]] > votes[pair2[0]][pair2[1]]
220 elsif votes[pair1[0]][pair1[1]] == votes[pair2[0]][pair2[1]] and
221 votes[pair1[1]][pair1[0]] > votes[pair2[1]][pair2[0]]
228 defeats.sort! do |pair1, pair2|
229 if is_weaker_defeat?( pair1, pair2 )
231 elsif is_weaker_defeat?( pair2, pair1 )
238 votes[defeats[0][0]][defeats[0][1]] = 0
239 votes[defeats[0][1]][defeats[0][0]] = 0