a number of improvements
[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           if self.verify_vote(vote)
46             self.tally_vote(vote)
47           else
48             raise InvalidVoteError.new("Invalid vote object", vote)
49           end
50         end
51       else
52         raise ElectionError, "Votes must be in the form of an array.", caller
53       end
54     end
55   end
56
57   protected
58   # by default, this is set to look if the vote is defined. it should
59   # be overridden in each class
60   def verify_vote(vote=nil)
61     vote ? true : false
62   end
63
64   # by default, this does nothing. it must be redefined in any subclass
65   def tally_vote(vote)
66     self.verify_vote(vote)
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 ? true : false
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 # ElectionResult and its subclasses are used to identify and report the results
112 # of an election. In almost all cases, these will be returned by the #results
113 # method of a corresponding ElectionVote subclass.
114 #
115 # Each ElectionResult object has the following methods:
116 #
117 #  * #winner? -- return Boolean as to the winner or winners of an election
118 #  * #winners -- an array of winners of the election
119 #  * #ranked_candidates -- (where available) a list of ranked candidates
120 class ElectionResult
121   attr_reader :winners
122   attr_reader :election
123
124   def initialize(voteobj=nil)
125     unless voteobj and voteobj.kind_of?( ElectionVote )
126       raise ArgumentError, "You must pass a ElectionVote array.", caller
127     end
128
129     @election = voteobj
130     @winners = Array.new
131   end
132
133   def winner
134     @winners[0] if @winners.length > 0
135   end
136
137   def winner?
138     @winners.length > 0 and not @winners[0].nil?
139   end
140   
141 end
142
143 class PluralityResult < ElectionResult
144   attr_reader :ranked_candidates
145   attr_reader :points
146   
147   def initialize(voteobj=nil)
148     super(voteobj)
149
150     votes = @election.votes
151     candidates = @election.candidates
152     
153     @ranked_candidates = votes.sort do |a, b|
154       b[1] <=> a[1]
155     end.collect {|a| a[0]}
156     
157     @points = @election.votes
158     
159     # winners are anyone who has the same number of votes as the
160     # first person
161     @winners = @ranked_candidates.find_all do |i|
162       votes[i] == votes[@ranked_candidates[0]]
163     end
164   end
165 end
166
167 # this class is complete because results for approval are computed
168 # identically to results from plurality
169 class ApprovalResult < PluralityResult
170 end
171   
172 class ElectionError < ArgumentError
173 end
174
175 class InvalidVoteError < ElectionError
176   attr_accessor :voteobj
177   def initialize(msg=nil, voteobj=nil)
178     super(msg)
179     @voteobj=voteobj
180   end
181 end

Benjamin Mako Hill || Want to submit a patch?