Initially, I wasn’t sure whether this was the appropriate place for this question, but after reading through the FAQ I feel fairly confident the topic is acceptable…. Furthermore I wasn’t sure if this would be classified as a particular type of problem (eg. a knapsack problem), thus, the title is rather vague. I’m sorry for that.
Anyways. As both practice for python, and an exercise to better understand general programming concepts, I’ve decided to write a simple Instant-Runoff Vote simulation. A description of instant Runoff voting can be found here: http://en.wikipedia.org/wiki/Instant-runoff_voting
Basically a voter ranks each candidate by assigning them a number, one being their first choice, two being their second choice etc… If at the end of voting no single candidate has a majority the candidate with the smallest share is eliminated and the votes that went to them go to the voters second choice candidate.
Assuming that there are five candidates and 20 voters, 100 votes (5×20) need to be cast, and each vote needs to be able point to the voter that cast it, and who the vote is for.
To represent this, I chose to use a nested list so that each sublist represented a single voter (or ballot), and each index of that sublist represented a single candidate.
Visualized:
[[1,3,2,5,4]…] So ballot[0][0] is voter 1’s vote for candidate 1
While I think this is a rather simple and effecient way to handle this (as far as I can tell) I am running into trouble when trying to:
a) Rank the candidates based upon the number of “1” votes they receive
b) Redistribute the votes after a candidate has been eliminated
I suppose with enough convoluted nested loops, and enough variables, I could achieve both of these, but not without the program becoming needlessly complex and confusing.
Here is the program so far…
#!usr/bin/python #Alt Voter Solution 3 import random ballots = [] results = [0,0,0,0,0] #Generate 20 ballots. Since each ballot has 5 seperate #unique numbers, I felt it would be best if I just #shuffled a list and appended it 20 times for voters in range(20): possible = [1,2,3,4,5] for x in range(1): shufvote = random.shuffle(possible) ballots.append(possible) for cand in range(5): for voter in ballots: if voter[cand] == 1: results[cand] +=1
So yeah, thats pretty much it. I’m thinking that part of my problem lies in how I choose to represent the data(in the nested lists). If anyone has any critiques and or suggestions, please share them! :D
Thanks
Advertisement
Answer
The following code uses a brute force approach (it is not optimized), but is quite robust:
#!usr/bin/env python import random import collections # Candidates: candidates = ['John', 'Max', 'Philip', 'Eric', 'Jane'] def simul_ballots(num_voters): """ Returns the (random) ballots of num_voters voters. """ ballots = [] choice = candidates[:] for _ in range(num_voters): random.shuffle(choice) ballots.append(choice[:]) # Copy return ballots def get_counts(ballots): """ Returns the number of votes for each candidate placed first in the ballots. Candidates present in the ballots but found in any first ballot places are given a count of zero. """ counts = dict() for ballot in ballots: vote = ballot[0] if vote in counts: counts[vote] += 1 else: counts[vote] = 1 # Python 2.7+ replacement for the above code: # counts = collections.Counter(ballot[0] for ballot in ballots) candidates = set() for ballot in ballots: candidates.update(ballot) for not_represented in set(candidates)-set(counts): counts[not_represented] = 0 return counts def get_winners(ballots): """ Returns the winners in the given ballots (lists of candidates), or [] if there is no winner. A winner is a candidate with 50 % or more of the votes, or a candidate with as many votes as all the other candidates. """ counts = get_counts(ballots) max_count = max(counts.values()) num_counts = sum(counts.values()) potential_winners = [candidate for (candidate, count) in counts.items() if count == max_count] if max_count >= num_counts/2. or len(potential_winners) == len(counts): return potential_winners else: return [] def get_losers(ballots): """ Returns the loser(s) of the ballots, i.e. the candidate(s) with the fewest voters. Returns [] if all candidates have the same number of votes. """ counts = get_counts(ballots) min_count = min(counts.values()) potential_losers = [candidate for (candidate, count) in counts.items() if count == min_count] if len(potential_losers) == len(counts): return [] else: return potential_losers def remove_candidate(ballots, candidate): """ Removes the given candidate from the ballots. """ for ballot in ballots: ballot.remove(candidate) if __name__ == '__main__': ballots = simul_ballots(20) while True: print "* Votes:" for ballot in ballots: print '-', ballot print "=> Counts:", get_counts(ballots) winners = get_winners(ballots) if winners: break # The losers are removed: losers = get_losers(ballots) print '=> Losers:', losers for loser in losers: remove_candidate(ballots, loser) print "Winners: ", winners
The output goes like this (with 4 candidates):
* Votes: - ['Max', 'John', 'Eric', 'Philip'] - ['Philip', 'Max', 'Eric', 'John'] - ['Eric', 'Philip', 'John', 'Max'] - ['Philip', 'John', 'Max', 'Eric'] - ['Eric', 'Max', 'Philip', 'John'] - ['Max', 'Philip', 'John', 'Eric'] - ['Max', 'John', 'Eric', 'Philip'] - ['Eric', 'Philip', 'Max', 'John'] - ['Max', 'Eric', 'Philip', 'John'] - ['Philip', 'Max', 'Eric', 'John'] - ['John', 'Eric', 'Max', 'Philip'] - ['Philip', 'Eric', 'Max', 'John'] - ['Max', 'Philip', 'John', 'Eric'] - ['Philip', 'Max', 'John', 'Eric'] - ['Philip', 'Eric', 'Max', 'John'] - ['John', 'Philip', 'Eric', 'Max'] - ['John', 'Max', 'Philip', 'Eric'] - ['Eric', 'Philip', 'John', 'Max'] - ['John', 'Eric', 'Philip', 'Max'] - ['Philip', 'John', 'Max', 'Eric'] => Counts: Counter({'Philip': 7, 'Max': 5, 'John': 4, 'Eric': 4}) => Losers: ['John', 'Eric'] * Votes: - ['Max', 'Philip'] - ['Philip', 'Max'] - ['Philip', 'Max'] - ['Philip', 'Max'] - ['Max', 'Philip'] - ['Max', 'Philip'] - ['Max', 'Philip'] - ['Philip', 'Max'] - ['Max', 'Philip'] - ['Philip', 'Max'] - ['Max', 'Philip'] - ['Philip', 'Max'] - ['Max', 'Philip'] - ['Philip', 'Max'] - ['Philip', 'Max'] - ['Philip', 'Max'] - ['Max', 'Philip'] - ['Philip', 'Max'] - ['Philip', 'Max'] - ['Philip', 'Max'] => Counts: Counter({'Philip': 12, 'Max': 8}) Winners: ['Philip']
This code can also use the collections module from Python 2.7+, as indicated in the comment.
Ties are automatically handled (all tied candidates are declared winners).
Possible optimizations include grouping the voters by ballots (if there are many more voters than possible ballots), and updating the counts by redistributing the counts from losers (instead of redoing a full recount). The above implementation provides a reference implementation whose results can be compared to optimized versions. :)