Python election challenge

Posted on

Problem

Basically, there is an election on 3 regions where either candidate needs to win 2 in order to win the election. Idea is to simulate 10000 elections to see which candidate wins. I think my solution works but I am not happy with the huge list of if statements and wondering of if there is a better way for this.

# Assignment simulate elections page 257

# 2 Candidates A and B

# Candidate A has following odds:
# 87% change of winning in region 1
# 65% changes of winning in region 2
# 17% changes of winning in region 3

from random import random

candidate_a_won = 0
candidate_b_won = 0

for i in range(0, 10000):
    result = random()
    candidate_a = 0
    candidate_b = 0
    if result + .87 >= 1:
        candidate_a += 1
    else:
        candidate_b += 1
    if result + .65 >= 1:
        candidate_a += 1
    else:
        candidate_b += 1
    if result + .17 >= 1:
        candidate_a += 1
    else:
        candidate_b += 1
    if candidate_a > candidate_b:
        candidate_a_won += 1
    else:
        candidate_b_won += 1

print('Candidate A won elections {} times, candidate B won elections {} times'.format(candidate_a_won, candidate_b_won))

Solution

  • Your code has a problem. Candidate A can loose region 1, but win region 3.
    Where with your code they can only win region 3 if they win region 2 and 1.
    This is as they are independent, and so you should random in each check.

  • You want to move your chances outside the loop into an array.

  • You can simplify your ifs into one comprehension.

  • Candidate a wins if they have more than half the regions, you don’t need to calculate the amount of regions candidate b wins.

And so you could change your code to the following:

from random import random

AMOUNT = 10000
region_chances = [87, 65, 17]
region_chances = [1 - n / 100 for n in region_chances]
regions = len(region_chances)
candidate_a_won = sum(
    sum(random() >= chance for chance in region_chances) * 2 > regions
    for _ in range(AMOUNT)
)
candidate_b_won = AMOUNT - candidate_a_won
print('Candidate A won elections {} times, candidate B won elections {} times'.format(candidate_a_won, candidate_b_won))

If you want to extend on the above then it’ll be hard if you decide to have more than two candidates, or even amounts of states.
But as this is a challenge, I don’t think you need to worry about these situations.

A slightly alternative approach is determining the chances for A to win beforehand, and then just do one election (not one per region).

The chances for A to win is the chance to win in region A and B, but not in region C; plus the chance of winning in region B and C but not A; …; plus the chance to win in all three regions:

AB(1C)+A(1B)C+(1A)BC+ABC=AB+AC+BC2ABC0.63AB(1C)+A(1B)C+(1A)BC+ABC=AB+AC+BC2ABC0.63

with A=0.87,B=0.65,C=0.17A=0.87,B=0.65,C=0.17

With this it becomes a bit easier:

amount = 10000
a, b, c = 0.87, 0.65, 0.17
chance = a*b + a*c + b*c - 2*a*b*c
candidate_a_won = sum(random() < chance for _ in range(amount))
candidate_b_won = amount - candidate_a_won
print('Candidate A won elections {} times, candidate B won elections {} times'.format(candidate_a_won, candidate_b_won))

This is a bit faster, but not as easily extendable to NN regions. For this we would need to derive a more general formula.

Per Graipher’s answer, I extended the chance of victory to accept an arbitrary number of regions and number of wins required.

from itertools import combinations
from operator import mul

def get_probability_of_victory(regs, required_wins):
    prob = 0.
    for wins in range(required_wins, len(regs) + 1):
        for comb in combinations(regs, wins):
            prob += reduce(mul, (regs[r] if r in comb else 1 - regs[r] for r in regs))
    return prob


regions = {'1': 0.87, '2': 0.65, '3': 0.17}
required_wins = 2

chance = get_probability_of_victory(regions, required_wins)
# continue with Graipher's code...

Leave a Reply

Your email address will not be published. Required fields are marked *