Skip to content

Commit 75f16d7

Browse files
committed
add worldcup to problems
1 parent b90cf1c commit 75f16d7

File tree

6 files changed

+435
-0
lines changed

6 files changed

+435
-0
lines changed

worldcup/.cs50.yml

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
lab50:
2+
files:
3+
- !exclude "*"
4+
- !include tournament.py
5+
- !include 2018m.csv
6+
- !include 2019w.csv
7+
8+
check50:
9+
files: &check50_files
10+
- !exclude "*"
11+
- !require tournament.py
12+
- !require answers.txt
13+
14+
submit50:
15+
files: *check50_files

worldcup/2018m.csv

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
team,rating
2+
Uruguay,976
3+
Portugal,1306
4+
France,1166
5+
Argentina,1254
6+
Brazil,1384
7+
Mexico,1008
8+
Belgium,1346
9+
Japan,528
10+
Spain,1162
11+
Russia,493
12+
Croatia,975
13+
Denmark,1054
14+
Sweden,889
15+
Switzerland,1179
16+
Colombia,989
17+
England,1040

worldcup/2019w.csv

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
team,rating
2+
Norway,1915
3+
Australia,2003
4+
England,2049
5+
Cameroon,1499
6+
France,2043
7+
Brazil,1944
8+
Spain,1913
9+
United States,2101
10+
Italy,1868
11+
China PR,1866
12+
Netherlands,1967
13+
Japan,1991
14+
Germany,2072
15+
Nigeria,1599
16+
Sweden,1962
17+
Canada,2006

worldcup/README.md

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# World Cup
2+
3+
Write a program to run simulations of the FIFA World Cup.
4+
5+
```
6+
$ python tournament.py 2018m.csv
7+
Belgium: 20.9% chance of winning
8+
Brazil: 20.3% chance of winning
9+
Portugal: 14.5% chance of winning
10+
Spain: 13.6% chance of winning
11+
Switzerland: 10.5% chance of winning
12+
Argentina: 6.5% chance of winning
13+
England: 3.7% chance of winning
14+
France: 3.3% chance of winning
15+
Denmark: 2.2% chance of winning
16+
Croatia: 2.0% chance of winning
17+
Colombia: 1.8% chance of winning
18+
Sweden: 0.5% chance of winning
19+
Uruguay: 0.1% chance of winning
20+
Mexico: 0.1% chance of winning
21+
```
22+
23+
## Background
24+
25+
* In soccer, teams are given [FIFA Ratings](https://en.wikipedia.org/wiki/FIFA_World_Rankings#Current_calculation_method), which are numerical values representing the team's relative skill levels. Higher FIFA ratings indicate better previous game results, and we can determine the probability that a team wins a matchup if we have both teams' FIFA ratings via this formula: $P(W_1) = \dfrac{1}{10^{(-\frac{T_2-T_1}{600})} + 1}$ where $P(W_1)$ is the probability that Team 1 wins, $T1$ is the FIFA rating for Team 1, and $T2$ is the FIFA rating for Team 2.
26+
27+
* In the World Cup, 16 teams begin in the knockout round. At each round, half the teams are eliminated. When two teams remain, the winner of the final match is the champion and the loser is the runner-up.
28+
29+
* The FIFA Ratings from just before the two previous World Cups are here: [May 2018 Men's FIFA Ratings](https://www.fifa.com/fifa-world-ranking/ranking-table/men/rank/id12189/) and [March 2019 Women's FIFA Ratings](https://www.fifa.com/fifa-world-ranking/ranking-table/women/rank/ranking_20190329/)
30+
31+
32+
## Implementation Details
33+
34+
* Complete the implementation of `tournament.py` at right, such that it simulates a number of tournaments and outputs each team's probability of winning, for all probabilities greater than `0%`.
35+
36+
* In `main()`, your program should read in the file indicated by the user into the list `bracket`. This list should be a list of dictionaries, each dictionary containing `team` and `rating` keys, where the `rating` is of type `int`.
37+
38+
* Two files are provided: `2018m.csv` and `2019w.csv`.
39+
40+
* In each, the bracket of teams, in order, is provided along with their FIFA Ratings.
41+
42+
* Specifically, in the first round, the team in line 1 will play the team in line 2. The team in line 3 will play the team in line 4. Since there are 16 total lines, the first round will contain 8 total games.
43+
44+
* In the second round, the winner between lines 1 and 2 will play the winner between lines 3 and 4, etc.
45+
46+
* The first few lines in `2018m.csv` look like this:
47+
48+
```
49+
team,rating
50+
Uruguay,976
51+
Portugal,1306
52+
France,1166
53+
```
54+
55+
* After, in `main()`, your program should simulate `N` tournaments, which, notice, has already been defined as `1000`. For each tournament, your program should return the winner and keep track of the number of wins for each team in the dictionary `counts`.
56+
57+
* In `simulate_tournament(bracket)`, your program should return the name of the winning team of one simulation of the given bracket. Your program should iterate through as many rounds as necessary to output one single winner of the tournament.
58+
59+
* In `simulate_round(bracket)`, your program should simulate a single round of the tournament and return the winners of each game in that round.
60+
61+
* Note that in the first round, the first team should play the second, the third team should play the fourth, and so on. The results of the game should appear in order, where the winner between the first two teams appears before the winner of the next two teams in the resulting list.
62+
63+
* Additionally, for example, if the bracket given has 4 teams (third round), your program should simulate 2 games and return a list of the 2 winning teams.
64+
65+
* Notice that `simulate_game(team1, team2)` has already been completed. Given two teams, it calculates the probability the first team defeats the second and returns `True` with that probability and `False` otherwise. We return `random.random() < probability` in this function, which is an application of Python's `random` module, where `random.random()` returns a pseudo-random value between 0 and 1.
66+
67+
68+
### Hints
69+
70+
* When reading in the file, you may find this syntax helpful, with `file_name` as the name of your file and `var` as a variable.
71+
72+
```python
73+
with open(file_name) as var:
74+
reader = csv.DictReader(var)
75+
```
76+
77+
* Recall that a dictionary looks like this: `{'team': 'Uruguay', 'rating': 976}`. If this dictionary had variable name `example`, then `example["team"] = Uruguay` and `example["rating"] = 976`.
78+
79+
* In Python, to add to a list, one can simply use the `.append()` function.
80+
81+
* To check if a key, `k`, exists in a dictionary, `dict`, one can write a simple conditional: `if k in dict:`.
82+
83+
* Regarding helper functions, you may find the following helpful:
84+
* In `main()`, simulate the tournament `N` times with `simulate_tournament()`.
85+
* In `simulate_tournament()`, repeatedly simulate rounds until only `1` team remains with `simulate_round()`.
86+
* In `simulate_round()`, simulate each game in that round with `simulate_game()`.
87+
88+
### How to Test Your Code
89+
90+
Your program should behave per the examples below. Since simulations have randomness within each, your output will be different from the examples below.
91+
92+
```
93+
$ python tournament.py 2018m.csv
94+
Belgium: 20.9% chance of winning
95+
Brazil: 20.3% chance of winning
96+
Portugal: 14.5% chance of winning
97+
Spain: 13.6% chance of winning
98+
Switzerland: 10.5% chance of winning
99+
Argentina: 6.5% chance of winning
100+
England: 3.7% chance of winning
101+
France: 3.3% chance of winning
102+
Denmark: 2.2% chance of winning
103+
Croatia: 2.0% chance of winning
104+
Colombia: 1.8% chance of winning
105+
Sweden: 0.5% chance of winning
106+
Uruguay: 0.1% chance of winning
107+
Mexico: 0.1% chance of winning
108+
```
109+
110+
```
111+
$ python tournament.py 2019w.csv
112+
Germany: 17.1% chance of winning
113+
United States: 14.8% chance of winning
114+
England: 14.0% chance of winning
115+
France: 9.2% chance of winning
116+
Canada: 8.5% chance of winning
117+
Japan: 7.1% chance of winning
118+
Australia: 6.8% chance of winning
119+
Netherlands: 5.4% chance of winning
120+
Sweden: 3.9% chance of winning
121+
Italy: 3.0% chance of winning
122+
Norway: 2.9% chance of winning
123+
Brazil: 2.9% chance of winning
124+
Spain: 2.2% chance of winning
125+
China PR: 2.1% chance of winning
126+
Nigeria: 0.1% chance of winning
127+
```
128+
129+
* You might be wondering what actually happened at the 2018 and 2019 World Cups! For Men's, France won, defeating Croatia in the final. Belgium defeated England for the third place position. For Women's, the United States won, defeating the Netherlands in the final. England defeated Sweden for the third place position.
130+
131+
{% next %}
132+
133+
## How to Submit
134+
135+
TODO

worldcup/__init__.py

+205
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
import os
2+
import re
3+
import check50
4+
import check50.py
5+
6+
BRACKET2 = [
7+
{"team": "Uruguay", "rating": 976},
8+
{"team": "Portugal", "rating": 1306},
9+
]
10+
BRACKET4 = [
11+
{"team": "Uruguay", "rating": 976},
12+
{"team": "Portugal", "rating": 1306},
13+
{"team": "France", "rating": 1166},
14+
{"team": "Argentina", "rating": 1254},
15+
]
16+
BRACKET8 = [
17+
{"team": "Uruguay", "rating": 976},
18+
{"team": "Portugal", "rating": 1306},
19+
{"team": "France", "rating": 1166},
20+
{"team": "Argentina", "rating": 1254},
21+
{"team": "Brazil", "rating": 1384},
22+
{"team": "Mexico", "rating": 1008},
23+
{"team": "Belgium", "rating": 1346},
24+
{"team": "Japan", "rating": 528},
25+
]
26+
BRACKET16 = [
27+
{"team": "Uruguay", "rating": 976},
28+
{"team": "Portugal", "rating": 1306},
29+
{"team": "France", "rating": 1166},
30+
{"team": "Argentina", "rating": 1254},
31+
{"team": "Brazil", "rating": 1384},
32+
{"team": "Mexico", "rating": 1008},
33+
{"team": "Belgium", "rating": 1346},
34+
{"team": "Japan", "rating": 528},
35+
{"team": "Spain", "rating": 1162},
36+
{"team": "Russia", "rating": 493},
37+
{"team": "Croatia", "rating": 975},
38+
{"team": "Denmark", "rating": 1054},
39+
{"team": "Sweden", "rating": 889},
40+
{"team": "Switzerland", "rating": 1179},
41+
{"team": "Colombia", "rating": 989},
42+
{"team": "England", "rating": 1040},
43+
]
44+
QUESTIONS = [
45+
"Which predictions, if any, proved incorrect as you increased the number of simulations?",
46+
'Suppose you\'re charged a fee for each second of compute time your program uses.\nAfter how many simulations would you call the predictions "good enough"?',
47+
]
48+
SIMULATION_RUNS = [
49+
"10",
50+
"100",
51+
"1000",
52+
"10000",
53+
"100000",
54+
"1000000",
55+
]
56+
57+
58+
@check50.check()
59+
def exists():
60+
"""tournament.py exists"""
61+
check50.exists("tournament.py", "answers.txt")
62+
check50.include("2018m.csv", "2019w.csv")
63+
64+
65+
@check50.check(exists)
66+
def imports():
67+
"""tournament.py imports"""
68+
check50.py.import_("tournament.py")
69+
70+
71+
@check50.check(imports)
72+
def sim_tournament_2():
73+
"""simulate_tournament handles a bracket of size 2"""
74+
check_tournament(BRACKET2)
75+
76+
77+
@check50.check(imports)
78+
def sim_tournament_4():
79+
"""simulate_tournament handles a bracket of size 4"""
80+
check_tournament(BRACKET4)
81+
82+
83+
@check50.check(imports)
84+
def sim_tournament_8():
85+
"""simulate_tournament handles a bracket of size 8"""
86+
check_tournament(BRACKET8)
87+
88+
89+
@check50.check(imports)
90+
def sim_tournament_16():
91+
"""simulate_tournament handles a bracket of size 16"""
92+
check_tournament(BRACKET16)
93+
94+
95+
@check50.check(imports)
96+
def counts():
97+
"""correctly keeps track of wins"""
98+
actual = check50.run("python3 tournament.py 2018m.csv").stdout()
99+
percents = re.findall("[0-9]*\.[0-9]", actual)
100+
percents = [float(x) for x in percents]
101+
if sum(percents) < 99 or sum(percents) > 101:
102+
raise check50.Failure("fails to keep track of wins")
103+
104+
105+
@check50.check(imports)
106+
def correct_teams1():
107+
"""correctly reports team information for Men's World Cup"""
108+
actual = check50.run("python3 tournament.py 2018m.csv").stdout()
109+
expected = ["Belgium", "Brazil", "Portugal", "Spain"]
110+
not_expected = ["Germany"]
111+
for team in expected:
112+
if team not in actual:
113+
raise check50.Failure(f"did not find team {team}")
114+
for team in not_expected:
115+
if team in actual:
116+
raise check50.Failure(f"incorrectly found team {team}")
117+
118+
119+
@check50.check(imports)
120+
def correct_teams2():
121+
"""correctly reports team information for Women's World Cup"""
122+
actual = check50.run("python3 tournament.py 2019w.csv").stdout()
123+
expected = ["Germany", "United States", "England"]
124+
not_expected = ["Belgium"]
125+
for team in expected:
126+
if team not in actual:
127+
raise check50.Failure(f"did not find team {team}")
128+
for team in not_expected:
129+
if team in actual:
130+
raise check50.Failure(f"incorrectly found team {team}")
131+
132+
percents = re.findall("[0-9]*\.[0-9]", actual)
133+
percents = [float(x) for x in percents]
134+
if sum(percents) < 99 or sum(percents) > 101:
135+
raise check50.Failure("fails to keep track of wins")
136+
137+
138+
@check50.check(imports)
139+
def check_answers():
140+
"""answers.txt is complete"""
141+
with open("answers.txt") as f:
142+
contents = f.read()
143+
144+
# Check timings
145+
for runs in SIMULATION_RUNS:
146+
match = re.search(
147+
rf"(?i){re.escape(runs)} simulations:\s*(\d+m\d+\.\d\d\ds)(?<!0m0\.000s)",
148+
contents,
149+
)
150+
if not match:
151+
raise check50.Failure(
152+
"answers.txt does not include timings for each number of simulation runs",
153+
help="Did you put all of your answers in 0m0.000s format?",
154+
)
155+
156+
# Check free response
157+
num_questions = len(QUESTIONS)
158+
for i, question in enumerate(QUESTIONS):
159+
160+
# Search for question, with at least 3 words afterwards
161+
if i + 1 < num_questions:
162+
163+
# Regex includes question being asked, response, and following question
164+
regex = (
165+
rf"(?i){re.escape(question)}"
166+
+ r":\s*(\S+\s+){3,}"
167+
+ rf"{re.escape(QUESTIONS[i + 1])}"
168+
)
169+
else:
170+
171+
# Last regex includes question being asked and response
172+
regex = rf"(?i){re.escape(question)}" + r":\s*(\S+\s+){3,}"
173+
174+
match = re.search(regex, contents)
175+
if not match:
176+
raise check50.Failure(
177+
"answers.txt does not include answers to free response questions",
178+
help="Did you write a sufficient response to each question?",
179+
)
180+
181+
182+
# Helpers
183+
184+
185+
def check_round(*args):
186+
tournament = check50.py.import_("tournament.py")
187+
actual = tournament.simulate_round(args[0])
188+
189+
for i in range(len(actual)):
190+
expected = [args[0][2 * i], args[0][2 * i + 1]]
191+
if not (actual[i] in expected):
192+
raise check50.Failure(
193+
"simulate_round fails to determine winners in a round"
194+
)
195+
196+
197+
def check_tournament(*args):
198+
tournament = check50.py.import_("tournament.py")
199+
actual = tournament.simulate_tournament(args[0])
200+
teams = [x["team"] for x in args[0]]
201+
202+
if not actual in teams:
203+
raise check50.Failure(
204+
"simulate_tournament fails to return the name of 1 winning team"
205+
)

0 commit comments

Comments
 (0)