Renamed readme file so that it reflects the type (reStructured Text).
[rubyvote] / lib / condorcet.rb
1 # election library -- a ruby library for elections
2 # copyright © 2005 MIT Media Lab and Benjamin Mako Hill
3
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.
8
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.
13
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
17 # 02110-1301, USA.
18
19 #################################################################
20 ## ==== condorcet.rb ====
21 ##
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 #################################################################
27
28 ##################################################################
29 ## CondorcetVote Classes and SubClasses
30 ##
31 ## The CondorcetVote class is subclassed by the PureCondorcetVote and
32 ## the CloneproofSSDVote classes but should not be used directly.
33
34 require 'election'
35
36 class CondorcetVote < ElectionVote
37
38   def tally_vote(vote=nil)
39
40     vote.each_with_index do |winner, index|
41       # only run through this if this *is* preferred to something
42       break if vote.length - 1 == index
43       losers = vote.last( vote.length - index )
44
45       losers.each do |loser|
46         next if winner == loser
47
48         @votes[winner] = Hash.new unless @votes.has_key?(winner)
49         @votes[loser] = Hash.new unless @votes.has_key?(loser)
50
51         if @votes[winner].has_key?(loser)
52           @votes[winner][loser] += 1
53         else
54           @votes[winner][loser] = 1
55         end
56
57         # make sure we have a comparable object
58         @votes[loser][winner] = 0 unless @votes[loser].has_key?( winner )
59
60         @candidates << loser unless @candidates.include?( loser )
61       end
62
63       @candidates << winner unless @candidates.include?( winner )
64     end
65   end
66
67   protected
68   def verify_vote(vote=nil)
69     vote.instance_of?( Array ) and
70       vote == vote.uniq
71   end
72   
73 end
74
75 class PureCondorcetVote < CondorcetVote
76   def result
77     PureCondorcetResult.new( self )
78   end
79 end
80
81 class CloneproofSSDVote < CondorcetVote
82   def result
83     CloneproofSSDResult.new( self )
84   end
85 end
86
87
88 ##################################################################
89 ## CondorcetResult Classes and SubClasses
90 ##
91 ## The CondorcetResult class is subclassed by the PureCondorcetResult
92 ## and the CloneproofSSDResult classes but should not be used
93 ## directly.
94
95 class CondorcetResult < ElectionResult
96   def initialize(voteobj=nil)
97     unless voteobj and voteobj.kind_of?( CondorcetVote )
98       raise ArgumentError, "You must pass a CondorcetVote array.", caller
99     end
100     super(voteobj)
101   end
102
103   protected
104   def defeats(candidates=nil, votes=nil)
105     candidates = @election.candidates unless candidates
106     votes = @election.votes unless votes
107
108     defeats = Array.new
109     candidates.each do |candidate|
110       candidates.each do |challenger|
111         next if candidate == challenger
112         if votes[candidate][challenger] > votes[challenger][candidate]
113           defeats << [ candidate, challenger ]
114         end
115       end
116     end
117
118     defeats
119   end
120
121 end
122
123 class PureCondorcetResult < CondorcetResult
124   def initialize(voteobj=nil)
125     super(voteobj)
126     self.condorcet()
127   end
128
129   protected
130   def condorcet
131     votes = @election.votes
132     candidates = @election.candidates
133
134     victors = Hash.new
135     candidates.each do |candidate|
136       victors[candidate] = Array.new
137     end
138
139     self.defeats.each do |pair|
140       winner, loser = *pair
141       victors[winner] << loser
142     end
143
144     winners = @election.candidates.find_all do |candidate|
145         victors[candidate].length == @election.candidates.length - 1
146     end
147
148     @winners << winners if winners.length == 1
149   end
150 end
151
152 class CloneproofSSDResult < CondorcetResult
153   def initialize(voteobj=nil)
154     super(voteobj)
155     @winners = self.cpssd()
156   end
157
158   protected
159   def cpssd
160     votes = @election.votes
161     candidates = *@election.candidates
162
163     def in_schwartz_set?(candidate, candidates, transitive_defeats)
164       candidates.each do |challenger|
165         next if candidate == challenger
166
167         if transitive_defeats.include?( [ challenger, candidate ] ) and
168             not transitive_defeats.include?( [ candidate, challenger ] )
169           return false
170         end
171       end
172       return true
173     end
174
175     loop do
176
177       # see the array with the standard defeats
178       transitive_defeats = self.defeats(candidates, votes)
179
180       candidates.each do |cand1|
181         candidates.each do |cand2|
182           candidates.each do |cand3|
183             if transitive_defeats.include?( [ cand2, cand1 ] ) and
184                 transitive_defeats.include?( [ cand1, cand3 ] ) and
185                 not transitive_defeats.include?( [ cand2, cand3 ] ) and
186                 not cand2 == cand3
187               transitive_defeats << [ cand2, cand3 ]
188             end
189           end
190         end
191       end
192
193       schwartz_set = Array.new
194       candidates.each do |candidate|
195         if in_schwartz_set?(candidate, candidates, transitive_defeats)
196           schwartz_set << candidate
197         end
198       end
199
200       candidates = schwartz_set
201
202       # look through the schwartz set now for defeats
203       defeats = self.defeats(candidates, votes)
204       
205       # it's a tie or there's only one option
206       break if defeats.length == 0
207
208       def is_weaker_defeat?( pair1, pair2 )
209         votes = @election.votes
210         if votes[pair1[0]][pair1[1]] > votes[pair2[0]][pair2[1]]
211           return true
212         elsif votes[pair1[0]][pair1[1]] == votes[pair2[0]][pair2[1]] and
213             votes[pair1[1]][pair1[0]] > votes[pair2[1]][pair2[0]]
214           return true
215         else
216           false
217         end
218       end
219       
220       defeats.sort! do |pair1, pair2|
221         if is_weaker_defeat?( pair1, pair2 ) 
222           +1
223         elsif is_weaker_defeat?( pair2, pair1 ) 
224           -1
225         else
226           0
227         end
228       end
229  
230       votes[defeats[0][0]][defeats[0][1]] = 0
231       votes[defeats[0][1]][defeats[0][0]] = 0
232       
233     end
234
235     return candidates
236   end
237
238 end

Benjamin Mako Hill || Want to submit a patch?