require File.dirname(__FILE__) + '/rubyvote/election'
require File.dirname(__FILE__) + '/rubyvote/condorcet'
require File.dirname(__FILE__) + '/rubyvote/positional'
-require File.dirname(__FILE__) + '/rubyvote/runoff'
+require File.dirname(__FILE__) + '/rubyvote/irv'
require File.dirname(__FILE__) + '/rubyvote/range'
class CondorcetVote < ElectionVote
- def tally_vote(vote=nil)
+ attr_accessor :results
+
+ def initialize(votes=nil)
+ unless defined?(@candidates)
+ @candidates = Array.new
+ votes.each do |vote_row|
+ vote_row = vote_row.flatten if vote_row.class == Array
+ vote_row.each do |vote|
+ @candidates << vote unless @candidates.include?(vote)
+ end
+ end
+ end
+ super(votes)
+ @results = Array.new
+ end
- vote.each_with_index do |winner, index|
- # only run through this if this *is* preferred to something
- break if vote.length - 1 == index
- losers = vote.last( vote.length - index )
+ def tally_vote(vote=nil)
- losers.each do |loser|
- next if winner == loser
+ vote.each_with_index do |winners, index|
+ if vote.flatten.length < @candidates.length
+ implied_losers = @candidates.select { |c| not vote.flatten.include?(c) }
+ vote.push(implied_losers)
+ end
+ if vote.length - 1 == index
+ losers = []
+ else
+ losers = vote.flatten.last( vote.flatten.length - index - 1)
+ end
- @votes[winner] = Hash.new unless @votes.has_key?(winner)
- @votes[loser] = Hash.new unless @votes.has_key?(loser)
+ losers.each do |place|
+ place = [place] unless place.class == Array
+ place.each do |loser|
+
+ winners = [winners] unless winners.class == Array
+ next if winners.include?(loser)
+ winners.each do |winner|
+ @votes[winner] = Hash.new unless @votes.has_key?(winner)
+ @votes[loser] = Hash.new unless @votes.has_key?(loser)
+
+ if @votes[winner].has_key?(loser)
+ @votes[winner][loser] += 1
+ else
+ @votes[winner][loser] = 1
+ end
- if @votes[winner].has_key?(loser)
- @votes[winner][loser] += 1
- else
- @votes[winner][loser] = 1
+ # make sure we have a comparable object
+ @votes[loser][winner] = 0 unless @votes[loser].has_key?( winner )
+ end
end
-
- # make sure we have a comparable object
- @votes[loser][winner] = 0 unless @votes[loser].has_key?( winner )
-
- @candidates << loser unless @candidates.include?( loser )
end
+ end
+ end
- @candidates << winner unless @candidates.include?( winner )
+ def results
+ if @results.size < 2 && (not @candidates.empty?)
+ tabulate
end
+ @results
+ end
+
+ def result
+ find_only_winner unless @winner
+ @winner
end
protected
+
def verify_vote(vote=nil)
vote.instance_of?( Array ) and
vote == vote.uniq
end
-
+
+ def tabulate
+ find_only_winner unless @winner
+ until @candidates.empty?
+ aResult = resultFactory( self )
+ @results << aResult.winners
+ filter_out(aResult)
+ end
+ end
+
+ def find_only_winner
+ @winner = resultFactory( self )
+ @results << @winner.winners
+ filter_out(@winner)
+ end
+
end
class PureCondorcetVote < CondorcetVote
- def result
- PureCondorcetResult.new( self )
+ def resultFactory(init)
+ PureCondorcetResult.new(init)
end
end
class CloneproofSSDVote < CondorcetVote
- def result
- CloneproofSSDResult.new( self )
+ def resultFactory(init)
+ CloneproofSSDResult.new(init)
end
+
end
super(voteobj)
end
+ protected
+
def defeats(candidates=nil, votes=nil)
candidates = @election.candidates unless candidates
votes = @election.votes unless votes
defeats = Array.new
+ candidates = [candidates] unless candidates.class == Array
candidates.each do |candidate|
candidates.each do |challenger|
next if candidate == challenger
end
protected
+
def condorcet
votes = @election.votes
candidates = @election.candidates
victors[winner] << loser
end
- winners = @election.candidates.find_all do |candidate|
- victors[candidate].length == @election.candidates.length - 1
+ victory_margin = 1
+ while true
+ winners = @election.candidates.find_all do |candidate|
+ victors[candidate].length == @election.candidates.length - victory_margin
+ end
+ if winners.length > 0
+ @winners = winners
+ return @winners
+ else
+ victory_margin += 1
+ end
end
-
- @winners = winners if winners.length == 1
end
end
end
protected
+
def cpssd
votes = @election.votes
candidates = *@election.candidates
# see the array with the standard defeats
transitive_defeats = self.defeats(candidates, votes)
+ defeats_hash = Hash.new
+ transitive_defeats.each { |td| defeats_hash[td] = 1 }
+ candidates = [candidates] unless candidates.class == Array
candidates.each do |cand1|
candidates.each do |cand2|
- candidates.each do |cand3|
- if transitive_defeats.include?( [ cand2, cand1 ] ) and
- transitive_defeats.include?( [ cand1, cand3 ] ) and
- not transitive_defeats.include?( [ cand2, cand3 ] ) and
- not cand2 == cand3
- transitive_defeats << [ cand2, cand3 ]
+ unless cand1 == cand2
+ candidates.each do |cand3|
+ if not cand2 == cand3 and
+ not cand1 == cand3 and
+ defeats_hash[[cand2, cand1]] and
+ defeats_hash[[cand1, cand3]] and
+ not defeats_hash[[cand2, cand3]]
+ transitive_defeats << [cand2, cand3]
+ defeats_hash[[cand2, cand3]] = 1
+ end
end
end
end
def tally_vote
self.verify_vote(vote)
end
+
+ def filter_out(winner)
+ @candidates.delete_if {|x| winner.winners.include?(x)}
+ end
+
end
class PluralityVote < ElectionVote
protected
def verify_vote(vote=nil)
- vote ? true : false
+ vote.instance_of?( String )
end
def tally_vote(candidate)
--- /dev/null
+class InstantRunoffVote < ElectionVote
+ def initialize(votes=nil)
+ @candidates = Array.new
+ votes.each do |vote|
+ @candidates = vote.uniq if vote.uniq.length > candidates.length
+ end
+ super(votes)
+ @candidates.each do |candidate|
+ @votes[candidate] = [0, Hash.new] unless @votes.has_key?(candidate)
+ end
+ end
+
+ def result(params={})
+ InstantRunoffResult.new(self, params)
+ end
+
+ protected
+ def tally_vote(vote)
+ votecopy = vote.dup
+ candidate = votecopy.shift
+ votes[candidate] = [0, Hash.new] unless votes.has_key?(candidate)
+ votes[candidate][0] += 1
+ if votes[candidate][1].has_key?(votecopy)
+ votes[candidate][1][votecopy] += 1
+ else
+ votes[candidate][1][votecopy] = 1
+ end
+ end
+
+ def verify_vote(vote=nil)
+ vote.instance_of?( Array ) and
+ vote == vote.uniq
+ end
+end
+
+class InstantRunoffLogicVote < InstantRunoffVote
+ def result(params={})
+ InstantRunoffLogicResult.new(self, params)
+ end
+end
+
+class InstantRunoffFirstRoundVote < InstantRunoffVote
+ def result(params={})
+ InstantRunoffFirstRoundResult.new(self, params)
+ end
+end
+
+class InstantRunoffAllVote < InstantRunoffVote
+ def result(params={})
+ InstantRunoffAllResult.new(self, params)
+ end
+end
+
+class InstantRunoffRandomVote < InstantRunoffVote
+ def result(params={})
+ InstantRunoffRandomResult.new(self, params)
+ end
+end
+
+class InstantRunoffResult < ElectionResult
+ attr_reader :ranked_candidates
+
+ def initialize(voteobj=nil, params={})
+ unless voteobj and voteobj.kind_of?( InstantRunoffVote )
+ raise ArgumentError, "You must pass an InstantRunoffVote array.", caller
+ end
+ super(voteobj)
+
+ votes = @election.votes.clone
+ candidates = @election.candidates
+ votes_sum = votes.inject(0) {|n, value| n + value[1][0]}
+ @majority = votes_sum/2 + 1
+ @ranked_candidates = Array.new()
+ ranked_candidates = Array.new()
+ losers = Array.new()
+
+ if params.has_key?('candidate_count')
+ apply_candidate_count(votes, params['candidate_count'])
+ end
+ if params.has_key?('vote_minimum')
+ apply_vote_minimum(votes, params['vote_minimum'])
+ end
+ if params.has_key?('percent_minimum')
+ apply_vote_minimum(votes, votes_sum * params['percent_minimum'])
+ end
+ if params.has_key?('percent_retention')
+ apply_retention(votes, votes_sum * params['percent_retention'])
+ end
+
+ begin
+ ranked_candidates = votes.sort do |a, b|
+ b[1][0] <=> a[1][0]
+ end.collect {|i| i[0]}
+ @winners = ranked_candidates.find_all do |i|
+ votes[i][0] >= @majority
+ end
+ end while not self.winner? and next_round(votes, ranked_candidates)
+ @ranked_candidates.unshift(*ranked_candidates)
+ end
+
+protected
+ def apply_candidate_count(votes, candidate_count)
+ if votes.size > candidate_count
+ losers = votes.sort do |a, b|
+ b[1][0] <=> a[1][0]
+ end.collect {|i| i[0]}.last(votes.size - candidate_count)
+ @ranked_candidates.unshift(losers) unless losers.empty?
+ losers.each { |loser| remove_candidate(votes, loser) }
+ end
+ end
+
+ def apply_vote_minimum(votes, vote_minimum)
+ losers = votes.find_all do |i|
+ i[1][0] < vote_minimum
+ end.collect {|i| i[0]}
+ if losers.length == votes.size
+ votes.clear
+ else
+ @ranked_candidates.unshift(losers) unless losers.empty?
+ losers.each { |loser| remove_candidate(votes, loser) }
+ end
+ end
+
+ def apply_retention(votes, retention)
+ losers = votes.sort do |a, b|
+ b[1][0] <=> a[1][0]
+ end.collect {|i| i[0]}
+ partial_sum = 0
+ while partial_sum < retention
+ partial_sum += votes[losers.shift][0]
+ end
+ @ranked_candidates.unshift(losers) unless losers.empty?
+ losers.each { |loser| remove_candidate(votes, loser) }
+ end
+
+ def next_round(votes, ranked_candidates)
+ loser = ranked_candidates[-1]
+ if votes.empty? or votes[loser][0] == votes[ranked_candidates[-2]][0]
+ false
+ else
+ @ranked_candidates.unshift(loser)
+ remove_candidate(votes, loser)
+ true
+ end
+ end
+
+ def remove_candidate(votes, loser)
+ votes.each_pair do |candidate, morevotes|
+ hash = morevotes[1]
+ hash.each_pair do |vote, count|
+ hash.delete(vote)
+ vote.delete(loser)
+ hash[vote] = count
+ end
+ end
+ votes[loser][1].each_pair do |vote, count|
+ candidate = vote.shift
+ votes[candidate][0] += count
+ if votes[candidate][1].has_key?(vote)
+ votes[candidate][1][vote] += count
+ else
+ votes[candidate][1][vote] = count
+ end
+ end
+ votes.delete(loser)
+ end
+end
+
+class InstantRunoffLogicResult < InstantRunoffResult
+ def next_round(votes, ranked_candidates)
+ losers = ranked_candidates.find_all do |i|
+ votes[i][0] == votes[ranked_candidates[-1]][0]
+ end
+ if losers.inject(0) {|n, loser| n + votes[loser][0]} >= @majority
+ false
+ else
+ @ranked_candidates.unshift(losers)
+ losers.each do |loser|
+ remove_candidate(votes, loser)
+ end
+ true
+ end
+ end
+end
+
+class InstantRunoffFirstRoundResult < InstantRunoffResult
+ def next_round(votes, ranked_candidates)
+ losers = ranked_candidates.find_all do |i|
+ votes[i][0] == votes[ranked_candidates[-1]][0]
+ end
+ loser = losers.sort do |a, b|
+ @election.votes[a][0] <=> @election.votes[b][0]
+ end.last
+ @ranked_candidates.unshift(loser)
+ remove_candidate(votes, loser)
+ end
+end
+
+class InstantRunoffAllResult < InstantRunoffResult
+ def next_round(votes, ranked_candidates)
+ losers = ranked_candidates.find_all do |i|
+ votes[i][0] == votes[ranked_candidates[-1]][0]
+ end
+ if losers.length == ranked_candidates.length
+ false
+ else
+ @ranked_candidates.unshift(losers)
+ losers.each do |loser|
+ remove_candidate(votes, loser)
+ end
+ true
+ end
+ end
+end
+
+class InstantRunoffRandomResult < InstantRunoffResult
+ def next_round(votes, ranked_candidates)
+ losers = ranked_candidates.find_all do |i|
+ votes[i][0] == votes[ranked_candidates[-1]][0]
+ end
+ loser = losers[rand(losers.length)]
+ @ranked_candidates.unshift(loser)
+ remove_candidate(votes, loser)
+ end
+end
+++ /dev/null
-class InstantRunoffVote < ElectionVote
- def initialize(votes=nil)
- @candidates = Array.new
- votes.each do |vote|
- @candidates = vote.uniq if vote.uniq.length > candidates.length
- end
- super(votes)
- @candidates.each do |candidate|
- @votes[candidate] = [0, Hash.new] unless @votes.has_key?(candidate)
- end
- end
-
- def result
- InstantRunoffResult.new(self)
- end
-
- protected
- def tally_vote(vote)
- votecopy = vote.dup
- candidate = votecopy.shift
- votes[candidate] = [0, Hash.new] unless votes.has_key?(candidate)
- votes[candidate][0] += 1
- if votes[candidate][1].has_key?(votecopy)
- votes[candidate][1][votecopy] += 1
- else
- votes[candidate][1][votecopy] = 1
- end
- end
-
- def verify_vote(vote=nil)
- vote.instance_of?( Array ) and
- vote == vote.uniq
- end
-end
-
-class InstantRunoffResult < ElectionResult
- attr_reader :ranked_candidates
-
- def initialize(voteobj=nil)
- unless voteobj and voteobj.kind_of?( InstantRunoffVote )
- raise ArgumentError, "You must pass an InstantRunoffVote array.", caller
- end
- super(voteobj)
-
- votes = @election.votes.clone
- candidates = @election.candidates
- majority = votes.inject(0) {|n, value| n + value[1][0]}/2 + 1
- @ranked_candidates = Array.new()
- ranked_candidates = Array.new()
-
- loop do
- ranked_candidates = votes.sort do |a, b|
- b[1][0] <=> a[1][0]
- end.collect {|i| i[0]}
- @winners = ranked_candidates.find_all do |i|
- votes[i][0] >= majority
- end
-
- loser = ranked_candidates[-1]
- break if self.winner? or votes[loser][0] == votes[ranked_candidates[-2]][0]
-
- @ranked_candidates.unshift(loser)
- runoff(votes, loser)
- end
- @ranked_candidates.unshift(*ranked_candidates)
- end
-
- def runoff(votes, loser)
- votes.each_pair do |candidate, morevotes|
- hash = morevotes[1]
- hash.each_pair do |vote, count|
- hash.delete(vote)
- vote.delete(loser)
- hash[vote] = count
- end
- end
- votes[loser][1].each_pair do |vote, count|
- candidate = vote.shift
- votes[candidate][0] += count
- if votes[candidate][1].has_key?(vote)
- votes[candidate][1][vote] += count
- else
- votes[candidate][1][vote] = count
- end
- end
- votes.delete(loser)
- end
-end