Reputation: 161
Hey guys I am trying to write code for a problem and I am having trouble deducing how I am supposed to do this.
So I have to write code for the Instant Run-Off Vote or Alternative voting system. basically what happens is that there is a nested list where each list inside is a ballot with ranked votes. So for example if the list looks like: ['REPUBLICAN', 'DEMOCRATIC', 'GREEN'] this means that for this ballot, the first choice was republican, then democratic, and then green. So essentially in a nested list there are multiple ballots and the function would need to create a count for all the parties mentioned that will show for how many ballots was a specific party the first choice. If there are 6 ballots, for example, and for three or more of those Republican is the first choice, the function should end. If no party has a majority, then the party with the lowest votes is eliminated and you recount the ballots for which that party was the first choice but this time you count for the second choice. You keep doing this until you have a majority and return a dictionary with count for all parties (the count will be 0 if the parties will be eliminated but must be returned).
Here is an example:
>>> count_irv([[’REP’], [’DEM’, ’REP’, ’LIB’], [’GRN’,’REP’], [’GRN’], [’REP’, ’DEM’], [’LIB’, ’DEM’, ’REP’], [’LIB’, ’CON’], [’GRN’, ’DEM’], [’REP’]])
{’LIB’: 0, ’CON’: 0, ’DEM’: 0, ’GRN’: 3, ’REP’: 5}
This is the code that I have so far:
def count_irv(ballots)
count = {}
for list in ballots:
for element in list:
if element in count:
count[element] += 1
else:
count[element] = 1
for key in count:
if count[key] >= len(ballots):
return count
else:
count[last_place(count)] = 0
return count
Where the last_place
function simply returns the key in the dictionary with the lowest value.
Using the code above, for the example provided, the code returns:
{'REP': 6, 'DEM': 4, 'LIB': 3, 'GRN': 3, 'CON': 0}
So essentially what I need help with is figuring out how to make my code to keep looping until there is a party with majority votes.
Also, I am new on here and so far enjoying my experience. However someone reported my last post for not putting the code in the right way and I got banned for like a day, so I would appreciate if there is something I should be doing differently, please leave it the comments below and I will be sure to make appropriate edits and be considerate of it for my next post. Thanks!
Upvotes: 0
Views: 972
Reputation: 149
let
ballots = [['REP'], ['DEM', 'REP', 'LIB'], ['GRN','REP'], ['GRN'], ['REP', 'DEM'], ['LIB', 'DEM', 'REP'], ['LIB', 'CON'], ['GRN', 'DEM'], ['REP']]
Short and simple solution will be
def count_irv(ballots):
count = {}
for ballot in ballots:
for item in ballot:
count[item] = 0
while True:
for ballot in ballots:
if len(ballot) > 0:
count[ballot[0]] += 1
non_empty_ballots = []
for ballot in ballots:
if len(ballot) > 0:
non_empty_ballots.append(ballot)
if max(count.values()) >= len(non_empty_ballots) / 2:
break
else:
pos_count = {}
for k, v in count.items():
if v > 0:
pos_count[k] = v
min_key = min(pos_count, key=pos_count.get)
for ballot in ballots:
if len(ballot) > 0 and ballot[0] == min_key:
ballot.remove(min_key)
for k in count:
count[k] = 0
return count
count_irv(ballots)
Out[114]: {'CON': 0, 'DEM': 0, 'GRN': 3, 'LIB': 0, 'REP': 5}
Upvotes: 0
Reputation: 12155
you could use a recursive function to count the votes, check for a winner and if no winner remove the loser and call the function again with the updated votes.
import random as r
from collections import Counter
def check_majority(voters):
majority = len(voters) // 2
print(f'Majority needed={majority}')
votes = (vote[0] for vote in voters if vote)
vote_count = Counter(votes).most_common()
print(f'The counts for votes in this round: {vote_count}')
winner = vote_count[0]
if winner[1] > majority:
print(f'The {winner[0]} party wins with a majority of {winner[1] - majority} votes')
return winner[0]
else:
loser = vote_count[-1]
print(f'The {loser[0]} party with only {loser[1]} votes has been eliminated')
for vote in voters:
if loser[0] in vote:
vote.remove(loser[0])
print(f'Progressing to next round of counts....\n')
check_majority(voters)
partys = ['Labour', 'Conservative', 'Libral Democrats', 'Green party', 'SNP']
num_partys = len(partys)
num_voters = r.randint(1000,10000)
voters = [r.sample(partys, r.randint(1,num_partys)) for _ in range(num_voters)]
print(f'samples generate: {len(voters)}')
check_majority(voters)
OUTPUT
samples generate: 7387
Majority needed=3693
The counts for votes in this round: Counter({'Labour': 1510, 'Libral Democrats': 1485, 'SNP': 1477, 'Conservative': 1475, 'Green party': 1440})
The Green party party with only 1440 votes has been eliminated
Progressing to next round of counts....
Majority needed=3693
The counts for votes in this round: Counter({'Labour': 1804, 'Libral Democrats': 1794, 'SNP': 1743, 'Conservative': 1742})
The Conservative party with only 1742 votes has been eliminated
Progressing to next round of counts....
Majority needed=3693
The counts for votes in this round: Counter({'Labour': 2228, 'Libral Democrats': 2215, 'SNP': 2170})
The SNP party with only 2170 votes has been eliminated
Progressing to next round of counts....
Majority needed=3693
The counts for votes in this round: Counter({'Labour': 2933, 'Libral Democrats': 2929})
The Libral Democrats party with only 2929 votes has been eliminated
Progressing to next round of counts....
Majority needed=3693
The counts for votes in this round: Counter({'Labour': 4436})
The Labour party wins with a majority of 743 votes
UPDATE
I am not sure why you wouldnt want to use collections module however you can replace this with a few lines of code to count each time you see a party and update dict counter value. Then sort the items in the dict as a list in order of highest to lowest
#count and sort the votes instaed of using collections
vote_count_dict = {}
for vote in votes:
if vote in vote_count_dict:
vote_count_dict[vote] += 1
else:
vote_count_dict[vote] = 1
vote_count = sorted(vote_count_dict.items(), key=lambda items: items[1], reverse=True)
Upvotes: 0
Reputation: 71512
This seems like a logic error more than a coding error. Right off the bat, I notice that this piece of code here:
for element in list:
if element in count:
count[element] += 1
else:
count[element] = 1
is going to register each person's second and third choice votes as part of the first choice totals.
I might suggest adding some print statements for debugging purposes so you can just read back a trace of how the counts update. Some comments and type annotations to help you follow what your code is supposed to be doing wouldn't hurt either!
Here's a very quick first pass over the function to make it easier to debug/read without changing the actual logic:
from collections import defaultdict
from typing import Dict, List
def count_irv(ballots: List[List[str]]) -> Dict[str, int]:
"""Takes a list of ballots, where each ballot is a list of ranked-choice votes
from highest to lowest priority. Returns a dictionary of vote totals after
applying IRV to find a majority choice."""
count: Dict[str, int] = defaultdict(int)
print("Counting %d ballots..." % len(ballots))
for ballot in ballots:
print("Applying ballot %r" % ballot)
for element in ballot:
count[element] += 1
print("Vote totals: %r" % dict(count))
count = dict(count) # convert to plain dict for easier pretty-printing
for key in count:
print("Applying IRV for the %r candidate" % key)
if count[key] >= len(ballots):
# this candidate... got more votes than there were ballots?
# how could this ever happen?
return count
else:
# find the candidate who got the least votes and zero their total out
count[min(count, key=count.get)] = 0
print("Vote totals: %r" % count)
return count
Since you didn't include your last_place
function I just replaced it with the builtin min
function that does the thing you describe; I also used a defaultdict
to simplify the initial count.
Running this code you can see that the vote totals start off wrong and the IRV logic doesn't have any hope of correcting them; it knocks out the bottom place candidate, but it doesn't transfer any of those votes to other candidates, and each time through the loop it's just zeroing the same last-place candidate. A better approach might be:
Upvotes: 1