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 shouldrandom
in each check. -
You want to move your chances outside the loop into an array.
-
You can simplify your
if
s 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(1−C)+A(1−B)C+(1−A)BC+ABC=AB+AC+BC−2ABC≈0.63
with A=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 N 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...