0

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!

markovv.sim
  • 161
  • 1
  • 8
  • Welcome to StackOverflow. [On topic](https://stackoverflow.com/help/on-topic), [how to ask](https://stackoverflow.com/help/how-to-ask), and ... [the perfect question](https://codeblog.jonskeet.uk/2010/08/29/writing-the-perfect-question/) apply here. We deal with *specific* coding questions; your open-ended "help me" *and* your meta-request for posting help are off-topic for SO. – Prune Nov 13 '19 at 17:42
  • 1
    ["Can Someone Help Me?" is not a valid SO question](https://meta.stackoverflow.com/questions/284236/why-is-can-someone-help-me-not-an-actual-question). This usually suggests that what you need is time with a local tutor or walk through a tutorial, rather than Stack Overflow. Best of all, each tutorial will teach you a collection of related techniques, rather than merely solving the immediate problem. You've asked for unfocused help in problem analysis and solution. – Prune Nov 13 '19 at 17:42
  • Help for your postings is available from the various links in the intro tour; I've given you some useful links in the first comment. Your general question is too broad; when you have a *specific* question about how to post, please direct it to the `meta` group, not `Python` and other tech groups. – Prune Nov 13 '19 at 17:44

3 Answers3

1

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:

  1. Count all the first-place votes (ONLY the first place votes)
  2. See if any has a majority (note that a majority is more than half the number of ballots, not more than the entire number of ballots, which is an impossibility) -- if so, done!
  3. Find the last-place candidate, and remove that candidate from all the ballots so that votes for that candidate will automatically roll over to the next choice (if any).
  4. Start over.
Samwise
  • 68,105
  • 3
  • 30
  • 44
  • I appreciate the response, however I am a beginner and I don't know a lot of stuff you have included in the code above. – markovv.sim Nov 13 '19 at 18:54
  • These are builtin functions so you can find the docs on them very easily; you can also run the code to verify that it does the exact same thing as your original code. :) `defaultdict(int)` just makes a dict that will automatically supply you an `int` value (0) when you access a key that doesn't exist yet. `min` finds the minimum in a collection (the `key=count.get` means that when it's comparing it should `get` the value from the dict rather than comparing the keys themselves). – Samwise Nov 13 '19 at 19:01
0

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)
Chris Doyle
  • 10,703
  • 2
  • 23
  • 42
  • Hey we have actually not learned about the collections module, is there a way to do it without using counter? – markovv.sim Nov 13 '19 at 18:59
  • This sounds more like a homework project. I would expect if you have been set this task you should know how to count number of items.... – Chris Doyle Nov 13 '19 at 19:11
0

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}
  • Hey is there any way you could try it without sets and loops inside like in a single line, sorry I am a beginner and I have limited knowledge. I understand a lot of the code except parts where you defined count in the beginning and pos_count and min_key. Thanks for the help regardless! – markovv.sim Nov 13 '19 at 18:57
  • simplified specially for you ) – Yeghishe Kerobyan Nov 13 '19 at 19:10