Top Level Namespace
Instance Method Summary collapse
- #baldwin(votes) ⇒ Object
- #borda(votes) ⇒ Object
- #borda_counts(votes, options) ⇒ Object
- #bucklin(votes) ⇒ Object
- #convert_legrand(legrand) ⇒ Object
- #coombs(votes) ⇒ Object
- #copeland(votes) ⇒ Object
- #instant_runoff(votes) ⇒ Object
- #normalize(votes) ⇒ Object
- #ranked_pairs(votes) ⇒ Object
- #results_from_pairs(sorted_pairs) ⇒ Object
- #scores_for_places(votes, places) ⇒ Object
- #winning_votes(votes) ⇒ Object
Instance Method Details
#baldwin(votes) ⇒ Object
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# File 'lib/systems/baldwin.rb', line 1 def baldwin votes votes, = normalize votes while true # calculate scores using Borda counts scores = borda_counts votes, # group and sort the options by score groups = .group_by {|option| scores[option]} sorted = groups.sort_by {|score, tied| -score} # puts # sorted.each {|score, tied| puts "#{score.to_f} #{tied}"} # find the options with the lowest score leasts = sorted.last[1] # if all remaining options are tied, they are the winners return leasts if leasts.size == .size # remove the options with the lowest score votes.collect! {|count, ranking| [count, ranking.collect {|tied| tied - leasts}] } -= leasts end end |
#borda(votes) ⇒ Object
1 2 3 4 5 6 7 8 9 10 |
# File 'lib/systems/borda.rb', line 1 def borda votes votes, = normalize votes # calculate scores using Borda counts scores = borda_counts votes, # sort the results and return the winner/s groups = .group_by {|option| scores[option]} sorted = groups.sort_by {|score, tied| -score} # sorted.each {|score, tied| puts "#{score.to_f} #{tied}"} sorted.first[1] end |
#borda_counts(votes, options) ⇒ Object
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# File 'lib/util.rb', line 1 def borda_counts votes, # calculate the Borda count of each option scores = Hash.new 0 votes.each {|count, ranking| # add to the scores remaining = .size ranking.each_with_index {|tied, i| # each tied option gets the average of their possible ranks average = remaining - (tied.size - 1) / 2r tied.each {|option| scores[option] += count * average} remaining -= tied.size } raise unless remaining == 0 # sanity check } scores end |
#bucklin(votes) ⇒ Object
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
# File 'lib/systems/bucklin.rb', line 23 def bucklin votes votes, = normalize votes sum_of_counts = votes.transpose[0].inject 0, :+ (1...size).each {|places| # count the votes up to places scores = scores_for_places votes, places # find the winners groups = .group_by {|option| scores[option]} # p groups best = groups.max_by {|score, tied| score} # if the best group has a majority, it is the winner return best[1] if best[0] > sum_of_counts / 2r } raise # should never get here, by the pigeonhole principle end |
#convert_legrand(legrand) ⇒ Object
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
# File 'lib/util.rb', line 18 def convert_legrand legrand # format from https://www.cse.wustl.edu/~legrand/rbvote/calc.html legrand.each_line.collect {|line| comment = line.index '#' line = line[0...comment] if comment fields = line.strip.split ':' case fields.size when 0 then next when 1 # no count provided, so default to 1 count = 1 ranks = fields[0] when 2 count = fields[0].to_i ranks = fields[1] else raise 'only one colon allowed per line' end groups = ranks.split '>' ranking = groups.collect {|group| group.split '='} [count, ranking] }.compact end |
#coombs(votes) ⇒ Object
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/systems/coombs.rb', line 1 def coombs votes # this implements the version of Coombs in which elimination proceeds # regardless of whether a candidate is ranked first by a majority of voters votes, remaining = normalize votes while true votes.each {|count, ranking| ranking.delete []} # p votes # see how many times each option was ranked last scores = Hash.new 0 votes.each {|count, ranking| tied = ranking.last tied.each {|option| scores[option] += count.quo tied.size} } # find the options which were ranked last the most groups = remaining.group_by {|option| scores[option]} # p groups mosts = groups.max_by {|score, tied| score}[1] # if all remaining options are tied, they are the winners return mosts if mosts.size == remaining.size # remove options which were ranked last the most votes.collect! {|count, ranking| [count, ranking.collect {|tied| tied - mosts}] } remaining -= mosts end end |
#copeland(votes) ⇒ Object
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
# File 'lib/systems/copeland.rb', line 1 def copeland votes # implements standard Copeland (pairwise victories minus pairwise defeats) votes, = normalize votes scores = winning_votes votes # how many times each option beat each other option # puts "scores: #{scores}" # calculate pairwise victories and pairwise defeats victories, defeats = {}, {} .each {|option1| victories[option1] = .select {|option2| scores[[option1, option2]] > scores[[option2, option1]] } defeats[option1] = .select {|option2| scores[[option1, option2]] < scores[[option2, option1]] } } # puts "victories: #{victories}" # puts "defeats: #{defeats}" # calculate results groups = .group_by {|option| victories[option].size - defeats[option].size } sorted = groups.sort_by {|margin, tied| -margin} # puts 'results:' # sorted.each {|margin, tied| puts " #{tied} --> #{margin}"} sorted.first[1] end |
#instant_runoff(votes) ⇒ Object
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
# File 'lib/systems/instant_runoff.rb', line 1 def instant_runoff votes votes, remaining = normalize votes while true votes.each {|count, ranking| ranking.delete []} # p votes # see how many times each option was ranked first scores = Hash.new 0 votes.each {|count, ranking| tied = ranking.first tied.each {|option| scores[option] += count.quo tied.size} } # find the options which were ranked first the least groups = remaining.group_by {|option| scores[option]} # p groups leasts = groups.min_by {|score, tied| score}[1] # if all remaining options are tied, they are the winners return leasts if leasts.size == remaining.size # remove options which were ranked first the least votes.collect! {|count, ranking| [count, ranking.collect {|tied| tied - leasts}] } remaining -= leasts end end |
#normalize(votes) ⇒ Object
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/util.rb', line 41 def normalize votes # normalize votes and return along with all options that appeared votes = convert_legrand votes if votes.is_a? String raise 'votes must contain at least one option' if votes.empty? raise 'each vote must have the form [count, ranking]' if votes.find {|vote| vote.size != 2} = votes.transpose[1].flatten.uniq votes = votes.collect {|count, ranking| raise 'ranking must be an array' unless ranking.is_a? Array # treat missing options as tied for last place missing = - ranking.flatten ranking = ranking + [missing] unless missing.empty? # make sure no options appear twice raise 'duplicate option in ranking' unless ranking.flatten.size == .size # normalize single ranks as singleton ties ranking = ranking.collect {|x| x.is_a?(Array) ? x : [x]} # remove empty ties ranking = ranking.select {|tied| not tied.empty?} [count, ranking] } [votes, ] end |
#ranked_pairs(votes) ⇒ Object
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/systems/ranked_pairs.rb', line 48 def ranked_pairs votes votes, = normalize votes scores = winning_votes votes # how many times each option beat each other option return if scores.empty? # every vote ranked all the options the same # group and sort the pairs by winning votes groups = scores.keys.group_by {|key| scores[key]} sorted = groups.sort_by {|winning_votes, pairs| -winning_votes} # puts # p sorted sorted_pairs = sorted.transpose[1] # puts # puts 'sorted pairs:' # sorted_pairs.each {|tied| p tied} # puts # puts 'calculating results...' results = results_from_pairs sorted_pairs results.first.to_a end |
#results_from_pairs(sorted_pairs) ⇒ Object
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
# File 'lib/systems/ranked_pairs.rb', line 19 def results_from_pairs sorted_pairs graph = RGL::DirectedAdjacencyGraph.new sorted_pairs.each {|tied| # puts # puts "graph is: " # puts graph # puts "tied is #{tied}" keep = tied.select {|winner, loser| # keep if there is no path from loser to winner, meaning that this edge # will not create a cycle next true if not graph.has_vertex? loser not graph.bfs_iterator(loser).include? winner } # puts "keep is #{keep}" keep.each {|winner, loser| graph.add_edge winner, loser} } # puts "final graph is: " # puts graph # puts condensed = graph.condensation_graph # puts "condensed is " # condensed.edges.each {|edge| p edge.to_a} # puts results = condensed.topsort_iterator.to_a # should be unique # puts "results is " # p results results end |
#scores_for_places(votes, places) ⇒ Object
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# File 'lib/systems/bucklin.rb', line 1 def scores_for_places votes, places # Add up votes for each option, including 1st-place votes, 2nd-place votes, # and so on up to k-place votes, where k = places. scores = Hash.new 0 votes.each {|count, ranking| remaining = places ranking.each {|tied| if remaining > tied.size # each tied option uses up one "place" tied.each {|option| scores[option] += count} remaining -= tied.size else # not enough remaining "places" to go around, so give each tied option # an equal fraction of what there is tied.each {|option| scores[option] += count * (remaining.quo tied.size)} break end } } scores end |
#winning_votes(votes) ⇒ Object
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/util.rb', line 63 def winning_votes votes # see how many times each option beat each other option scores = Hash.new 0 votes.each {|count, ranking| (0...ranking.size).each {|i| (i+1...ranking.size).each {|j| ranking[i].each {|option1| ranking[j].each {|option2| scores[[option1, option2]] += count } } } } } scores end |