Merge jdong to pull in new rubyvote
[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
137   end
138
139 end
140
141 class PluralityResult < ElectionResult
142   attr_reader :ranked_candidates
143
144   def initialize(voteobj=nil)
145     super(voteobj)
146
147     votes = @election.votes
148     candidates = @election.candidates
149     
150     @ranked_candidates = votes.sort do |a, b|
151       b[1] <=> a[1]
152     end.collect {|a| a[0]}
153     
154     # winners are anyone who has the same number of votes as the
155     # first person
156     @winners = @ranked_candidates.find_all do |i|
157       votes[i] == votes[@ranked_candidates[0]]
158     end
159   end
160 end
161
162 # this class is complete because results for approval are computed
163 # identically to results from plurality
164 class ApprovalResult < PluralityResult
165 end
166   
167 class ElectionError < ArgumentError
168 end
169
170 class InvalidVoteError < ElectionError
171   attr_accessor :voteobj
172   def initialize(msg=nil, voteobj=nil)
173     super(msg)
174     @voteobj=voteobj
175   end
176 end

Benjamin Mako Hill || Want to submit a patch?