#filter_out and PureCondorcet weren't getting along well for sparse elections
[rubyvote] / lib / rubyvote / election.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 ## ==== election.rb ====
21 ##
22 ## This file contains the core ElectionVote and ElectionResults
23 ## classes and the most common and simple election methods including
24 ## plurality and approval voting.
25 #################################################################
26
27 ##################################################################
28 ## ElectionVote Classes and SubClasses
29 ##
30 ## There classes are used to store, verify, and "tally" (i.e. count
31 ## votes for the standard Election superclass and for the most common
32 ## types of elections.
33
34 class ElectionVote
35   attr_reader :votes
36   attr_reader :candidates
37
38   def initialize(votes=nil)
39     @votes = Hash.new unless defined?(@votes)
40     @candidates = Array.new unless defined?(@candidates)
41
42     if votes
43       if votes.instance_of?( Array )
44         votes.each do |vote|
45           self.tally_vote(vote) if self.verify_vote(vote)
46         end
47       else
48         raise ElectionError, "Votes must be in the form of an array.", caller
49       end
50     end
51   end
52
53   protected
54   # by default, this is set to look if the vote is defined. it should
55   # be overridden in each class
56   def verify_vote(vote=nil)
57     vote ? true : false
58   end
59
60   # by default, this does nothing. it must be redefined in any subclass
61   def tally_vote
62     self.verify_vote(vote)
63   end
64
65   def filter_out(winner)
66     @candidates.delete_if {|x| winner.winners.include?(x)}
67   end
68
69 end
70
71 class PluralityVote < ElectionVote
72   def result
73     PluralityResult.new(self)
74   end
75   
76   protected
77   def verify_vote(vote=nil)
78     vote.instance_of?( String )
79   end
80
81   def tally_vote(candidate)
82     if @votes.has_key?(candidate)
83       @votes[candidate] += 1
84     else
85       @votes[candidate] = 1
86       @candidates << candidate
87     end
88   end
89 end
90
91 class ApprovalVote < PluralityVote
92   def result
93     ApprovalResult.new(self)
94   end
95
96   protected
97   def verify_vote(vote=nil)
98     vote.instance_of?( Array ) and vote.length >= 1
99   end
100
101   def tally_vote(approvals)
102     approvals.each {|candidate| super(candidate)}
103   end
104 end
105
106
107 ##################################################################
108 ## Election Result Classes
109 ##
110
111 ## There classes are used to compute and report the results of an
112 ## election. In almost all cases, these will be returned by the
113 ## #results method of a corresponding ElectionVote subclass.
114
115 class ElectionResult
116   attr_reader :winners
117   attr_accessor :full_results
118
119   def initialize(voteobj=nil)
120     unless voteobj and voteobj.kind_of?( ElectionVote )
121       raise ArgumentError, "You must pass a ElectionVote array.", caller
122     end
123
124     @election = voteobj
125     @winners = Array.new
126     @full_results = Array.new
127   end
128
129   def winner
130     @winners[0] if @winners.length > 0
131   end
132
133   def winner?
134     @winners.length > 0
135   end
136
137   def get_full_results
138     @full_results.collect {|x| x.winners}
139   end
140
141 end
142
143 class PluralityResult < ElectionResult
144   attr_reader :ranked_candidates
145
146   def initialize(voteobj=nil)
147     super(voteobj)
148
149     votes = @election.votes
150     candidates = @election.candidates
151     
152     @ranked_candidates = votes.sort do |a, b|
153       b[1] <=> a[1]
154     end.collect {|a| a[0]}
155     
156     # winners are anyone who has the same number of votes as the
157     # first person
158     @winners = @ranked_candidates.find_all do |i|
159       votes[i] == votes[@ranked_candidates[0]]
160     end
161   end
162 end
163
164 # this class is complete because results for approval are computed
165 # identically to results from plurality
166 class ApprovalResult < PluralityResult
167 end
168   
169 class ElectionError < ArgumentError
170 end
171

Benjamin Mako Hill || Want to submit a patch?