]> projects.mako.cc - selectricity/blob - lib/rubyvote/runoff.rb
Changed the README to include Justin's name.
[selectricity] / lib / rubyvote / runoff.rb
1 class InstantRunoffVote < ElectionVote
2   def initialize(votes=nil)
3     @candidates = Array.new
4     votes.each do |vote|
5       @candidates = vote.uniq if vote.uniq.length > candidates.length
6     end
7     super(votes)
8     @candidates.each do |candidate|
9       @votes[candidate] = [0, Hash.new] unless @votes.has_key?(candidate)
10     end
11   end
12
13   def result
14     InstantRunoffResult.new(self)
15   end
16   
17   protected
18   def tally_vote(vote)
19     votecopy = vote.dup
20     candidate = votecopy.shift
21     votes[candidate] = [0, Hash.new] unless votes.has_key?(candidate)
22     votes[candidate][0] += 1
23     if votes[candidate][1].has_key?(votecopy)
24       votes[candidate][1][votecopy] += 1
25     else
26       votes[candidate][1][votecopy] = 1
27     end
28   end
29
30   def verify_vote(vote=nil)
31     vote.instance_of?( Array ) and
32       vote == vote.uniq
33   end
34 end
35
36 class InstantRunoffResult < ElectionResult
37   attr_reader :ranked_candidates
38
39   def initialize(voteobj=nil)
40     unless voteobj and voteobj.kind_of?( InstantRunoffVote )
41       raise ArgumentError, "You must pass an InstantRunoffVote array.", caller
42     end
43     super(voteobj)
44
45     votes = @election.votes.clone
46     candidates = @election.candidates
47     majority = votes.inject(0) {|n, value| n + value[1][0]}/2 + 1
48     @ranked_candidates = Array.new()
49     ranked_candidates = Array.new()
50
51     loop do
52       ranked_candidates = votes.sort do |a, b|
53         b[1][0] <=> a[1][0]
54       end.collect {|i| i[0]}
55       @winners = ranked_candidates.find_all do |i|
56         votes[i][0] >= majority
57       end
58       
59       loser = ranked_candidates[-1]
60       break if self.winner? or votes[loser][0] == votes[ranked_candidates[-2]][0]
61
62       @ranked_candidates.unshift(loser)
63       runoff(votes, loser) 
64     end
65     @ranked_candidates.unshift(*ranked_candidates)
66   end
67
68   def runoff(votes, loser)
69     votes.each_pair do |candidate, morevotes|
70       hash = morevotes[1]
71       hash.each_pair do |vote, count|
72         hash.delete(vote)
73         vote.delete(loser)
74         hash[vote] = count
75       end
76     end
77     votes[loser][1].each_pair do |vote, count|
78       candidate = vote.shift
79       votes[candidate][0] += count
80       if votes[candidate][1].has_key?(vote)
81         votes[candidate][1][vote] += count
82       else
83         votes[candidate][1][vote] = count
84       end
85     end
86     votes.delete(loser)
87   end
88 end

Benjamin Mako Hill || Want to submit a patch?