]> projects.mako.cc - selectricity/blob - lib/rubyvote/election.rb
* Add a few testcases for checking for nonexistent things
[selectricity] / 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
66     self.verify_vote(vote)
67   end
68
69   def filter_out(winner)
70     @candidates.delete_if {|x| winner.winners.include?(x)}
71   end
72
73 end
74
75 class PluralityVote < ElectionVote
76   def result
77     PluralityResult.new(self)
78   end
79   
80   protected
81   def verify_vote(vote=nil)
82     vote ? true : false
83   end
84
85   def tally_vote(candidate)
86     if @votes.has_key?(candidate)
87       @votes[candidate] += 1
88     else
89       @votes[candidate] = 1
90       @candidates << candidate
91     end
92   end
93 end
94
95 class ApprovalVote < PluralityVote
96   def result
97     ApprovalResult.new(self)
98   end
99
100   protected
101   def verify_vote(vote=nil)
102     vote.instance_of?( Array ) and vote.length >= 1
103   end
104
105   def tally_vote(approvals)
106     approvals.each {|candidate| super(candidate)}
107   end
108 end
109
110
111 ##################################################################
112 ## Election Result Classes
113 ##
114
115 ## There classes are used to compute and report the results of an
116 ## election. In almost all cases, these will be returned by the
117 ## #results method of a corresponding ElectionVote subclass.
118
119 class ElectionResult
120   attr_reader :winners
121
122   def initialize(voteobj=nil)
123     unless voteobj and voteobj.kind_of?( ElectionVote )
124       raise ArgumentError, "You must pass a ElectionVote array.", caller
125     end
126
127     @election = voteobj
128     @winners = Array.new
129   end
130
131   def winner
132     @winners[0] if @winners.length > 0
133   end
134
135   def winner?
136     @winners.length > 0 and not @winners[0].nil?
137   end
138
139 end
140
141 class PluralityResult < ElectionResult
142   attr_reader :ranked_candidates
143   attr_reader :points
144   
145   def initialize(voteobj=nil)
146     super(voteobj)
147
148     votes = @election.votes
149     candidates = @election.candidates
150     
151     @ranked_candidates = votes.sort do |a, b|
152       b[1] <=> a[1]
153     end.collect {|a| a[0]}
154     
155     @points = @election.votes
156     
157     # winners are anyone who has the same number of votes as the
158     # first person
159     @winners = @ranked_candidates.find_all do |i|
160       votes[i] == votes[@ranked_candidates[0]]
161     end
162   end
163 end
164
165 # this class is complete because results for approval are computed
166 # identically to results from plurality
167 class ApprovalResult < PluralityResult
168 end
169   
170 class ElectionError < ArgumentError
171 end
172
173 class InvalidVoteError < ElectionError
174   attr_accessor :voteobj
175   def initialize(msg=nil, voteobj=nil)
176     super(msg)
177     @voteobj=voteobj
178   end
179 end

Benjamin Mako Hill || Want to submit a patch?