(ML; 60 min)
Store your answers in machine-learning/analysis/logistic_regression.py
Suppose that you have a dataset of points $(x,y)$ where $x$ is the number of hours that a player has practiced Space Empires and $y$ is their probability of winning against an average player.
data = [(10, 0.05), (100, 0.35), (1000, 0.95)]
Fit a function $y=\dfrac{1}{1+e^{\beta_0 + \beta_1 x}}$ to the following data, using matrix methods involving the pseudoinverse.
a) Write the system of equations:
__ = 1/(1 + e^(beta_0 + beta_1 * ___) )
__ = 1/(1 + e^(beta_0 + beta_1 * ___) )
__ = 1/(1 + e^(beta_0 + beta_1 * ___) )
b) Convert the system to a system of linear equations:
beta_0 + beta_1 * ___ = ___
beta_0 + beta_1 * ___ = ___
beta_0 + beta_1 * ___ = ___
c) Solve for beta_0
and beta_1
using the pseudoinverse.
d) For a player who has practiced 500 hours, compute the probability of winning against an average player.
e) How many hours does an average player practice? Tip: an average player will have 0.5 probability of winning against an average player, so you just need to solve the equation 0.5 = 1/(1 + e^(beta_0 + beta_1 * x) )
for x
.
(Algo; 45 min)
Implement breadth-first search in your Graph
class. This will be very similar to the breadth-first search in your Tree
class, but now you will have to make sure that you do not revisit any nodes (because now there are multiple paths from one node to another).
Example:
>>> edges = [(0,1),(1,2),(1,3),(3,4),(1,4),(4,5)]
>>> vertices = ['a','b','c','d','e','f']
>>> graph = Graph(edges, vertices)
>>> graph.breadth_first_search(2) (note: 2 is the index of the starting node)
[2, 1, 0, 3, 4, 5] (note: in class, we printed out the values, but let's instead return a list of the indices)
other answers are valid, e.g. [2, 1, 4, 3, 0, 5]
(Space Empires; 90 min)
Note: this is the same problem as the previous assignment -- that means you've got an extra class to catch up on it, and it also means that you should indeed fully catch up on it.
Implement the following additional tests in tests/test_combat_test_player.py
:
At the end of Turn 1 Economic Phase:
At the end of Turn 2 Movement Phase:
At the end of Turn 2 Combat Phase:
At the end of Turn 2 Economic Phase:
At the end of Turn 3 Movement Phase:
At the end of Turn 3 Combat Phase:
At the end of Turn 3 Economic Phase:
Note: These tests are based on the following game events file. I've done my best to ensure this is accurate, but if your game doesn't match up and you think there might be an error in the events, please post about it.
STARTING CONDITIONS
Players 1 and 2
are CombatTestingPlayers
start with 20 CPs
have an initial fleet of 3 scouts, 3 colony ships, 4 ship yards
---
TURN 1 - MOVEMENT PHASE
Player 1:
Scouts 1,2,3: (2,0) --> (2,2)
Colony Ships 4,5,6: (2,0) --> (2,2)
Player 2:
Scouts 1,2,3: (2,4) --> (2,2)
Colony Ships 4,5,6: (2,4) --> (2,2)
COMBAT PHASE
Colony Ships are removed
| PLAYER | SHIP | HEALTH |
----------------------------------------
| 1 | Scout 1 | 1 |
| 1 | Scout 2 | 1 |
| 1 | Scout 3 | 1 |
| 2 | Scout 1 | 1 |
| 2 | Scout 2 | 1 |
| 2 | Scout 3 | 1 |
Attack 1
Attacker: Player 1 Scout 1
Defender: Player 2 Scout 1
Largest Roll to Hit: 3
Die Roll: 1
Hit or Miss: Hit
| PLAYER | SHIP | HEALTH |
---------------------------------------------
| 1 | Scout 1 | 1 |
| 1 | Scout 2 | 1 |
| 1 | Scout 3 | 1 |
| 2 | Scout 2 | 1 |
| 2 | Scout 3 | 1 |
Attack 2
Attacker: Player 1 Scout 2
Defender: Player 2 Scout 2
Largest Roll to Hit: 3
Die Roll: 2
Hit or Miss: Hit
| PLAYER | SHIP | HEALTH |
---------------------------------------------
| 1 | Scout 1 | 1 |
| 1 | Scout 2 | 1 |
| 1 | Scout 3 | 1 |
| 2 | Scout 3 | 1 |
Attack 3
Attacker: Player 1 Scout 3
Defender: Player 2 Scout 3
Largest Roll to Hit: 3
Dice Roll: 3
Hit or Miss: Hit
| PLAYER | SHIP | HEALTH |
---------------------------------------------
| 1 | Scout 1 | 1 |
| 1 | Scout 2 | 1 |
| 1 | Scout 3 | 1 |
Combat phase complete
------------------------
TURN 1 - ECONOMIC PHASE
Player 1
INCOME/MAINTENANCE (starting CP: 20)
colony income: +3 CP/Colony x 1 Colony = +3 CP
maintenance costs: -1 CP/Scout x 3 Scouts = -3 CP
PURCHASES (starting CP: 20)
ship size technology 2: -10 CP
destroyer: -9 CP
REMAINING CP: 1
Player 2
INCOME/MAINTENANCE (starting CP: 20)
colony income: +3 CP/Colony x 1 Colony = +3 CP
maintenance costs: 0
PURCHASES (starting CP: 23)
ship size technology 2: -10 CP
destroyer: -9 CP
REMAINING CP: 4
------------------------
TURN 2 - MOVEMENT PHASE
Player 1:
Scouts 1,2,3: stay at (2,2)
Destroyer 1 : (2,0) --> (2,2)
Player 2:
Destroyer 1: (2,4) --> (2,2)
------------------------
TURN 2 - COMBAT PHASE
| PLAYER | SHIP | HEALTH |
---------------------------------------------
| 1 | Destroyer 1 | 1 |
| 2 | Destroyer 1 | 1 |
| 1 | Scout 1 | 1 |
| 1 | Scout 2 | 1 |
| 1 | Scout 3 | 1 |
Attack 1
Attacker: Player 1 Destroyer 1
Defender: Player 2 Destroyer 1
Largest Roll to Hit: 4
Dice Roll: 4
Hit or Miss: Hit
| PLAYER | SHIP | HEALTH |
---------------------------------------------
| 1 | Destroyer 1 | 1 |
| 1 | Scout 1 | 1 |
| 1 | Scout 2 | 1 |
| 1 | Scout 3 | 1 |
------------------------
TURN 2 - ECONOMIC PHASE
Player 1
INCOME/MAINTENANCE (starting CP: 1)
colony income: +3 CP/Colony x 1 Colony = +3 CP
maintenance costs: -1 CP/Scout x 3 Scouts -1 CP/Destroyer x 1 Destroyer = -4 CP
REMAINING CP: 0
Player 2
INCOME/MAINTENANCE (starting CP: 4)
colony income: +3 CP/Colony x 1 Colony = +3 CP
maintenance costs: 0
PURCHASES (starting CP: 7)
scout: -6 CP
REMAINING CP: 1
------------------------
TURN 3 - MOVEMENT PHASE
Player 1:
Scouts 1,2,3: stay at (2,2)
Destroyer 1 : stay at (2,2)
Player 2:
Scout 1: (2,4) --> (2,2)
------------------------
TURN 3 - COMBAT PHASE
| PLAYER | SHIP | HEALTH |
---------------------------------------------
| 1 | Destroyer 1 | 1 |
| 1 | Scout 1 | 1 |
| 1 | Scout 2 | 1 |
| 1 | Scout 3 | 1 |
| 2 | Scout 1 | 1 |
Attack 1
Attacker: Player 1 Destroyer 1
Defender: Player 2 Scout 1
Largest Roll to Hit: 4
Dice Roll: 5
Hit or Miss: Miss
Attack 2
Attacker: Player 1 Scout 1
Defender: Player 2 Scout 1
Largest Roll to Hit: 3
Dice Roll: 6
Hit or Miss: Miss
Attack 3
Attacker: Player 1 Scout 2
Defender: Player 2 Scout 1
Largest Roll to Hit: 3
Dice Roll: 1
Hit or Miss: Hit
| PLAYER | SHIP | HEALTH |
---------------------------------------------
| 1 | Destroyer 1 | 1 |
| 1 | Scout 1 | 1 |
| 1 | Scout 2 | 1 |
| 1 | Scout 3 | 1 |
------------------------
TURN 3 - ECONOMIC PHASE
Player 1
INCOME/MAINTENANCE (starting CP: 0)
colony income: +3 CP/Colony x 1 Colony = +3 CP
maintenance costs: -1 CP/Scout x 3 Scouts -1 CP/Destroyer x 1 Destroyer = -4 CP
REMOVALS
remove scout 3 due to inability to pay maintenance costs
REMAINING CP: 0
Player 2
INCOME/MAINTENANCE (starting CP: 1)
colony income: +3 CP/Colony x 1 Colony = +3 CP
maintenance costs: 0
REMAINING CP: 4
(Stats; 45 min)
For this problem, put your code in assignment-problems/src/conditional_probability.py
During class, each person created a distribution of coin flips.
flips = {
'George': 'THTH HTHT THTH HHTH THTH TTHT THTH TTTH THTH TTHT THHT HTTH THTH THHT THHT THTH THTH THHT THTH THTH',
'David': 'TTHH HHTT HHTT TTHH HTHT THTH THTH THTH HTHT HTHT THTH HTHT THHH THTH HHTT HHTT TTTH HHTH HTHH TTTH',
'Elijah': 'THTT HTHT HTHH HHHT TTHH THHH TTTT HHTT TTTH TTHH HTHT HTHT TTTT HTTT TTTH HTTT TTHH THTH THHH HTHH',
'Colby': 'HTTH HTHH THTT HHTH TTHT HTTT THHH TTHH HHTT THTH HHTT THTH THHH TTHH THTT HHTH HTTH HTHH TTHT HTTT',
'Justin': 'THTT HTHT TTTH THHT HTHH TTTH THTH HHTH TTTT HTTH HHTT THHH HHHH THTH HTTH TTHH HTHT HHHT THHT TTTH'
}
For each person, print out the following probabilities:
$P(\text{ next flip was heads | previous flip was heads })$
$P(\text{ next flip was tails | previous flip was heads })$
$P(\text{ next flip was heads | previous flip was tails })$
$P(\text{ next flip was tails | previous flip was tails })$
Also, print out the probabilities on the simple dataset HHTTHT
to double check that your code is correct.
For the dataset HHTTHT
, the pairs are HH
, HT
, TT
, TH
, HT
. So,
$P(\text{ next flip was heads | previous flip was heads }) = 1/3$
$P(\text{ next flip was tails | previous flip was heads }) = 2/3$
$P(\text{ next flip was heads | previous flip was tails }) = 1/2$
$P(\text{ next flip was tails | previous flip was tails }) = 1/2$
Note: Using Bayes' Rule,
$$P(X \, | \, Y) = \dfrac{P(X \wedge Y)}{P(Y)} = \dfrac{\text{count}(X \wedge Y)}{\text{count}(Y)},$$we have that
$$\begin{align*} P(\text{ next flip was heads | previous flip was tails }) &= \dfrac{\text{count}(\text{ next flip was heads AND previous flip was tails })}{\text{count}(\text{ previous flip was tails })} \\ &= \dfrac{\text{count}(\text{ consecutive pairs in which tails was followed IMMEDIATELY by heads })}{\text{count}(\text{ consecutive pairs in which previous flip was tails })}. \end{align*}$$(Algo; 45 min)
Implement depth-first search in your Graph
class. This will be very similar to the depth-first search in your Tree
class, but now you will have to make sure that you do not revisit any nodes (because now there are multiple paths from one node to another).
Example:
>>> edges = [(0,1),(1,2),(1,3),(3,4),(1,4)]
>>> vertices = ['a','b','c','d','e']
>>> graph = Graph(edges, vertices)
>>> graph.depth_first_search(2) (note: 2 is the index of the starting node)
[2, 1, 3, 4, 0] (note: in class, we printed out the values, but let's instead return a list of the indices)
other answers are valid, e.g. [2, 1, 0, 4, 3]
(Space Empires; 90 min)
Implement the following additional tests in tests/test_combat_test_player.py
:
At the end of Turn 1 Economic Phase:
At the end of Turn 2 Movement Phase:
At the end of Turn 2 Combat Phase:
At the end of Turn 2 Economic Phase:
At the end of Turn 3 Movement Phase:
At the end of Turn 3 Combat Phase:
At the end of Turn 3 Economic Phase:
Note: These tests are based on the following game events file. I've done my best to ensure this is accurate, but if your game doesn't match up and you think there might be an error in the events, please post about it.
STARTING CONDITIONS
Players 1 and 2
are CombatTestingPlayers
start with 20 CPs
have an initial fleet of 3 scouts, 3 colony ships, 4 ship yards
---
TURN 1 - MOVEMENT PHASE
Player 1:
Scouts 1,2,3: (2,0) --> (2,2)
Colony Ships 4,5,6: (2,0) --> (2,2)
Player 2:
Scouts 1,2,3: (2,4) --> (2,2)
Colony Ships 4,5,6: (2,4) --> (2,2)
COMBAT PHASE
Colony Ships are removed
| PLAYER | SHIP | HEALTH |
----------------------------------------
| 1 | Scout 1 | 1 |
| 1 | Scout 2 | 1 |
| 1 | Scout 3 | 1 |
| 2 | Scout 1 | 1 |
| 2 | Scout 2 | 1 |
| 2 | Scout 3 | 1 |
Attack 1
Attacker: Player 1 Scout 1
Defender: Player 2 Scout 1
Largest Roll to Hit: 3
Die Roll: 1
Hit or Miss: Hit
| PLAYER | SHIP | HEALTH |
---------------------------------------------
| 1 | Scout 1 | 1 |
| 1 | Scout 2 | 1 |
| 1 | Scout 3 | 1 |
| 2 | Scout 2 | 1 |
| 2 | Scout 3 | 1 |
Attack 2
Attacker: Player 1 Scout 2
Defender: Player 2 Scout 2
Largest Roll to Hit: 3
Die Roll: 2
Hit or Miss: Hit
| PLAYER | SHIP | HEALTH |
---------------------------------------------
| 1 | Scout 1 | 1 |
| 1 | Scout 2 | 1 |
| 1 | Scout 3 | 1 |
| 2 | Scout 3 | 1 |
Attack 3
Attacker: Player 1 Scout 3
Defender: Player 2 Scout 3
Largest Roll to Hit: 3
Dice Roll: 3
Hit or Miss: Hit
| PLAYER | SHIP | HEALTH |
---------------------------------------------
| 1 | Scout 1 | 1 |
| 1 | Scout 2 | 1 |
| 1 | Scout 3 | 1 |
Combat phase complete
------------------------
TURN 1 - ECONOMIC PHASE
Player 1
INCOME/MAINTENANCE (starting CP: 20)
colony income: +3 CP/Colony x 1 Colony = +3 CP
maintenance costs: -1 CP/Scout x 3 Scouts = -3 CP
PURCHASES (starting CP: 20)
ship size technology 2: -10 CP
destroyer: -9 CP
REMAINING CP: 1
Player 2
INCOME/MAINTENANCE (starting CP: 20)
colony income: +3 CP/Colony x 1 Colony = +3 CP
maintenance costs: 0
PURCHASES (starting CP: 23)
ship size technology 2: -10 CP
destroyer: -9 CP
REMAINING CP: 4
------------------------
TURN 2 - MOVEMENT PHASE
Player 1:
Scouts 1,2,3: stay at (2,2)
Destroyer 1 : (2,0) --> (2,2)
Player 2:
Destroyer 1: (2,4) --> (2,2)
------------------------
TURN 2 - COMBAT PHASE
| PLAYER | SHIP | HEALTH |
---------------------------------------------
| 1 | Destroyer 1 | 1 |
| 2 | Destroyer 1 | 1 |
| 1 | Scout 1 | 1 |
| 1 | Scout 2 | 1 |
| 1 | Scout 3 | 1 |
Attack 1
Attacker: Player 1 Destroyer 1
Defender: Player 2 Destroyer 1
Largest Roll to Hit: 4
Dice Roll: 4
Hit or Miss: Hit
| PLAYER | SHIP | HEALTH |
---------------------------------------------
| 1 | Destroyer 1 | 1 |
| 1 | Scout 1 | 1 |
| 1 | Scout 2 | 1 |
| 1 | Scout 3 | 1 |
------------------------
TURN 2 - ECONOMIC PHASE
Player 1
INCOME/MAINTENANCE (starting CP: 1)
colony income: +3 CP/Colony x 1 Colony = +3 CP
maintenance costs: -1 CP/Scout x 3 Scouts -1 CP/Destroyer x 1 Destroyer = -4 CP
REMAINING CP: 0
Player 2
INCOME/MAINTENANCE (starting CP: 4)
colony income: +3 CP/Colony x 1 Colony = +3 CP
maintenance costs: 0
PURCHASES (starting CP: 7)
scout: -6 CP
REMAINING CP: 1
------------------------
TURN 3 - MOVEMENT PHASE
Player 1:
Scouts 1,2,3: stay at (2,2)
Destroyer 1 : stay at (2,2)
Player 2:
Scout 1: (2,4) --> (2,2)
------------------------
TURN 3 - COMBAT PHASE
| PLAYER | SHIP | HEALTH |
---------------------------------------------
| 1 | Destroyer 1 | 1 |
| 1 | Scout 1 | 1 |
| 1 | Scout 2 | 1 |
| 1 | Scout 3 | 1 |
| 2 | Scout 1 | 1 |
Attack 1
Attacker: Player 1 Destroyer 1
Defender: Player 2 Scout 1
Largest Roll to Hit: 4
Dice Roll: 5
Hit or Miss: Miss
Attack 2
Attacker: Player 1 Scout 1
Defender: Player 2 Scout 1
Largest Roll to Hit: 3
Dice Roll: 6
Hit or Miss: Miss
Attack 3
Attacker: Player 1 Scout 2
Defender: Player 2 Scout 1
Largest Roll to Hit: 3
Dice Roll: 1
Hit or Miss: Hit
| PLAYER | SHIP | HEALTH |
---------------------------------------------
| 1 | Destroyer 1 | 1 |
| 1 | Scout 1 | 1 |
| 1 | Scout 2 | 1 |
| 1 | Scout 3 | 1 |
------------------------
TURN 3 - ECONOMIC PHASE
Player 1
INCOME/MAINTENANCE (starting CP: 0)
colony income: +3 CP/Colony x 1 Colony = +3 CP
maintenance costs: -1 CP/Scout x 3 Scouts -1 CP/Destroyer x 1 Destroyer = -4 CP
REMOVALS
remove scout 3 due to inability to pay maintenance costs
REMAINING CP: 0
Player 2
INCOME/MAINTENANCE (starting CP: 1)
colony income: +3 CP/Colony x 1 Colony = +3 CP
maintenance costs: 0
REMAINING CP: 4
(Stats; 45 min)
For this problem, put your code in assignment-problems/src/approximations_of_randomness.py
During class, each person created a distribution of coin flips.
flips = {
'George': 'THTH HTHT THTH HHTH THTH TTHT THTH TTTH THTH TTHT THHT HTTH THTH THHT THHT THTH THTH THHT THTH THTH',
'David': 'TTHH HHTT HHTT TTHH HTHT THTH THTH THTH HTHT HTHT THTH HTHT THHH THTH HHTT HHTT TTTH HHTH HTHH TTTH',
'Elijah': 'THTT HTHT HTHH HHHT TTHH THHH TTTT HHTT TTTH TTHH HTHT HTHT TTTT HTTT TTTH HTTT TTHH THTH THHH HTHH',
'Colby': 'HTTH HTHH THTT HHTH TTHT HTTT THHH TTHH HHTT THTH HHTT THTH THHH TTHH THTT HHTH HTTH HTHH TTHT HTTT',
'Justin': 'THTT HTHT TTTH THHT HTHH TTTH THTH HHTH TTTT HTTH HHTT THHH HHHH THTH HTTH TTHH HTHT HHHT THHT TTTH'
}
a) Treating these coin flips as simulations of 20 samples of 4 flips, compute the KL divergence between each simulation and the true distribution. Print out your results.
b) Print out the answer to the following question: whose simulation was the best approximation of true random coin flipping?
(Algo; 90 min)
a) In the file graph/src/node.py
, create a class Node
that
is initialized with an index
, and
has the attributes index
, value
, and neighbors
.
b) In the file graph/tests/test_node.py
, assert
that your Node
class passes the following tests. (If you want to remove the >>>
at the beginning, use this text replacement tool.)
>>> string_node = Node(0)
>>> string_node.index
0
>>> string_node.set_value('asdf')
>>> string_node.value
'asdf'
>>> string_node.neighbors
[]
>>> numeric_node = Node(1)
>>> numeric_node.set_value(765)
>>> numeric_node.set_neighbor(string_node)
>>> numeric_node.neighbors[0].value
'asdf'
>>> string_node.neighbors[0].value
765
>>> array_node = Node(2)
>>> array_node.set_value([[1,2],[3,4]])
>>> array_node.set_neighbor(numeric_node)
>>> array_node.neighbors[0].value
765
>>> numeric_node.neighbors[1].value
[[1,2],[3,4]]
c) In the file graph/src/graph.py
, create a class Graph
that
is initialized with list of edges
(an edge is a tuple of indices) and vertices
(a vertex is a node value), and
has the attribute nodes
.
d) In the file graph/tests/test_graph.py
, assert
that your class passes the following tests.
>>> edges = [(0,1),(1,2),(1,3),(3,4),(1,4)]
>>> vertices = ['a','b','c','d','e']
>>> graph = Graph(edges, vertices)
>>> [node.index for node in graph.nodes]
[0, 1, 2, 3, 4]
>>> [node.value for node in graph.nodes]
['a','b','c','d','e']
>>> [len(node.neighbors) for node in graph.nodes]
[1, 4, 1, 2, 2]
(Space Empires; 60 min)
Continue writing the game events log, up until a combat round occurs in which some player has a destroyer. (You can stop writing the log once you finish that combat round.)
With regards to the die rolls, each combat round should pick up where the previous round left off. So since the first combat ended with a die roll of 3, the second combat will begin with a die roll of 4.
STARTING CONDITIONS
Players 1 and 2
are CombatTestingPlayers
start with 20 CPs
have an initial fleet of 3 scouts and 3 colony ships
---
TURN 1
MOVEMENT PHASE
Player 1, Movement 1
Scouts 1,2,3: (2,0) --> (2, 1)
Colony Ships 4,5,6: (2,0) --> (2,1)
Player 2, Movement 1
Scouts 1,2,3: (2,4) --> (2, 3)
Colony Ships 4,5,6: (2,4) --> (2,3)
Player 1, Movement 2
Scout 1,2,3: (2, 1) --> (2, 2)
Player 2, Movement 2
Scouts 1,2,3: (2, 3) --> (2, 2)
Colony Ships 4,5,6: (2,1) --> (2,2)
Players 1/2, Movement 3
No player moved!
Player 1, Final Locations:
Scout 1: (2, 2)
Scout 2: (2, 2)
Scout 3: (2, 2)
Colony Ship 4: (2,2)
Colony Ship 5: (2,2)
Colony Ship 6: (2,2)
Player 2, Final Locations:
Scout 1: (2, 2)
Scout 2: (2, 2)
Scout 3: (2, 2)
Colony Ship 4: (2,2)
Colony Ship 5: (2,2)
Colony Ship 6: (2,2)
COMBAT PHASE
Remaining ships: (list in the table below)
ATTACKING ORDER | PLAYER | SHIP | HEALTH |
---------------------------------------------------------
1 | 1 | Scout 1 | 1 |
2 | 1 | Scout 2 | 1 |
3 | 1 | Scout 3 | 1 |
4 | 2 | Scout 1 | 1 |
5 | 2 | Scout 2 | 1 |
6 | 2 | Scout 3 | 1 |
Attack 1
Attacker: Player 1 Scout 1
Defender: Player 2 Scout 1
Miss Threshold: 3
Die Roll: 1
Hit or Miss: Hit (since Die Roll <= Miss Threshold)
ATTACKING ORDER | PLAYER | SHIP | HEALTH |
-------------------------------------------------------------
1 | 1 | Scout 1 | 1 |
2 | 1 | Scout 2 | 1 |
3 | 1 | Scout 3 | 1 |
4 | 2 | Scout 2 | 1 |
5 | 2 | Scout 3 | 1 |
Attack 2
Attacker: Player 1 Scout 2
Defender: Player 2 Scout 2
Hit Threshold: 3
Die Roll: 2
Hit or Miss: Hit (since Die Roll <= Miss Threshold)
ATTACKING ORDER | PLAYER | SHIP | HEALTH |
-------------------------------------------------------------
1 | 1 | Scout 1 | 1 |
2 | 1 | Scout 2 | 1 |
3 | 1 | Scout 3 | 1 |
5 | 2 | Scout 3 | 1 |
Attack 3
Attacker: Player 1 Scout 3
Defender: Player 2 Scout 3
Hit Threshold: 3
Dice Roll: 3
Hit or Miss: Hit (since Die Roll <= Miss Threshold)
ATTACKING ORDER | PLAYER | SHIP | HEALTH |
-------------------------------------------------------------
1 | 1 | Scout 1 | 1 |
2 | 1 | Scout 2 | 1 |
3 | 1 | Scout 3 | 1 |
Combat phase complete
ECONOMIC PHASE
... (continue here)
(ML; 60 min)
Create a file machine-learning/analysis/sandwich_ratings_dummy_variables.py
to store your code/answers for this problem.
Slices Beef | Tbsp Peanut Butter | Condiments | Rating |
--------------------------------------------------------------
0 | 0 | - | 1 |
0 | 0 | mayo | 1 |
0 | 0 | jelly | 4 |
0 | 0 | mayo, jelly | 0 |
5 | 0 | - | 4 |
5 | 0 | mayo | 8 |
5 | 0 | jelly | 1 |
5 | 0 | mayo, jelly | 0 |
0 | 5 | - | 5 |
0 | 5 | mayo | 0 |
0 | 5 | jelly | 9 |
0 | 5 | mayo, jelly | 0 |
5 | 5 | - | 0 |
5 | 5 | mayo | 0 |
5 | 5 | jelly | 0 |
5 | 5 | mayo, jelly | 0 |
a) Replace the Condiments
feature with the dummy variables Mayo (M)
and Jelly (J)
. Print out the resulting array.
B = slices roast beef
P = tbsp peanut butter
M = mayo
J = jelly
R = rating
B P M J R
[[ 0, 0, 0, 0, 1 ],
[ 0, 0, 1, 0, 1 ],
[ 0, 0, 0, 1, 4 ],
[ 0, 0, 1, 1, 0 ],
[ 5, 0, _, _, 4 ],
[ 5, 0, _, _, 8 ],
[ 5, 0, _, _, 1 ],
[ 5, 0, _, _, 0 ],
[ 0, 5, _, _, 5 ],
[ 0, 5, _, _, 0 ],
[ 0, 5, _, _, 9 ],
[ 0, 5, _, _, 0 ],
[ 5, 5, _, _, 0 ],
[ 5, 5, _, _, 0 ],
[ 5, 5, _, _, 0 ],
[ 5, 5, _, _, 0 ]]
b) Include interaction features. Print out the resulting array.
B P M J BP BM BJ PM PJ MJ R
[[ 0, 0, 0, 0, __, __, __, __, __, __, 1 ],
[ 0, 0, 1, 0, __, __, __, __, __, __, 1 ],
[ 0, 0, 0, 1, __, __, __, __, __, __, 4 ],
[ 0, 0, 1, 1, __, __, __, __, __, __, 0 ],
[ 5, 0, _, _, __, __, __, __, __, __, 4 ],
[ 5, 0, _, _, __, __, __, __, __, __, 8 ],
[ 5, 0, _, _, __, __, __, __, __, __, 1 ],
[ 5, 0, _, _, __, __, __, __, __, __, 0 ],
[ 0, 5, _, _, __, __, __, __, __, __, 5 ],
[ 0, 5, _, _, __, __, __, __, __, __, 0 ],
[ 0, 5, _, _, __, __, __, __, __, __, 9 ],
[ 0, 5, _, _, __, __, __, __, __, __, 0 ],
[ 5, 5, _, _, __, __, __, __, __, __, 0 ],
[ 5, 5, _, _, __, __, __, __, __, __, 0 ],
[ 5, 5, _, _, __, __, __, __, __, __, 0 ],
[ 5, 5, _, _, __, __, __, __, __, __, 0 ]]
c) To help you ensure that your transformed dataset is correct, I'll tell you that the sum of all the numbers in your matrix, INCLUDING the ratings, should be equal to 313.
assert
that the above statement is true.
d) Fit a linear model to the data
rating = beta_0
+ beta_1 ( slices beef ) + beta_2 ( tbsp pb ) + beta_3 ( mayo ) + beta_4 ( jelly )
+ beta_5 ( slices beef ) ( tbsp pb ) + beta_6 ( slices beef ) ( jelly ) + beta_7 ( slices beef ) ( jelly )
+ beta_8 ( tbsp pb ) ( mayo ) + beta_9 ( tbsp pb ) ( jelly )
+ beta_10 ( mayo ) ( jelly )
print out your coefficients, labeled with the corresponding variables:
COEFFICIENTS
bias term: ___
beef: ___
peanut butter: ___
mayo: ___
jelly: ___
beef & peanut butter: ___
beef & mayo: ___
beef & jelly: ___
peanut butter & mayo: ___
peanut butter & jelly: ___
mayo & jelly: ___
...
e) Print out the following predictions:
PREDICTED RATINGS
2 slices beef + mayo: __
2 slices beef + jelly: __
3 tbsp peanut butter + jelly: __
3 tbsp peanut butter + jelly + mayo: ___
2 slices beef + 3 tbsp peanut butter + jelly + mayo: ___
(Stats; 30 min)
Create a file assignment-problems/detecting_biased_coins.py
to store your code/answers for this problem.
Suppose that you run an experiment where you flip a coin 3 times, and repeat that trial 25 times. You run this experiment on 3 different coins, and get the following results:
coin_1 = ['TTH', 'HHT', 'HTH', 'TTH', 'HTH', 'TTH', 'TTH', 'TTH', 'THT', 'TTH', 'HTH', 'HTH', 'TTT', 'HTH', 'HTH', 'TTH', 'HTH', 'TTT', 'TTT', 'TTT', 'HTT', 'THT', 'HHT', 'HTH', 'TTH']
coin_2 = ['HTH', 'HTH', 'HTT', 'THH', 'HHH', 'THH', 'HHH', 'HHH', 'HTT', 'TTH', 'TTH', 'HHT', 'TTH', 'HTH', 'HHT', 'THT', 'THH', 'THT', 'TTH', 'TTT', 'HHT', 'THH', 'THT', 'THT', 'TTT']
coin_3 = ['HHH', 'THT', 'HHT', 'HHT', 'HTH', 'HHT', 'HHT', 'HHH', 'TTT', 'THH', 'HHH', 'HHH', 'TTH', 'THH', 'THH', 'TTH', 'HTT', 'TTH', 'HTT', 'HHT', 'TTH', 'HTH', 'THT', 'THT', 'HTH']
Let $P_i(x)$ be the probability of getting $x$ heads in a trial of 3 tosses, using the $i$th coin. Plot the distributions $P_1(x),$ $P_2(x),$ and $P_3(x)$ on the same graph. Be sure to label them.
Based on the plot of the distributions, what conclusions can you make about the coins? For each coin, does it appear to be fair, biased towards heads, or biased towards tails? Write your answer as a comment.
(Space Empires; 90 min)
Write a testing file tests/test_combat_testing_player.py
to ensure that a game on a $5 \times 5$ grid with two CombatTestingPlayer
s proceeds correctly.
At the end of Turn 1 Movement Phase:
At the end of Turn 1 Combat Phase:
Note: These tests are based on Elijah's game events file, which we verified during class.
STARTING CONDITIONS
Players 1 and 2
are CombatTestingPlayers
start with 20 CPs
have an initial fleet of 3 scouts and 3 colony ships
---
TURN 1
MOVEMENT PHASE
Player 1, Movement 1
Scouts 1,2,3: (2,0) --> (2, 1)
Colony Ships 4,5,6: (2,0) --> (2,1)
Player 2, Movement 1
Scouts 1,2,3: (2,4) --> (2, 3)
Colony Ships 4,5,6: (2,4) --> (2,3)
Player 1, Movement 2
Scout 1,2,3: (2, 1) --> (2, 2)
Colony Ships 4,5,6: (2,1) --> (2,2)
Player 2, Movement 2
Scouts 1,2,3: (2, 3) --> (2, 2)
Colony Ships 4,5,6: (2,1) --> (2,2)
Players 1/2, Movement 3
No player moved!
Player 1, Final Locations:
Scout 1: (2, 2)
Scout 2: (2, 2)
Scout 3: (2, 2)
Colony Ship 4: (2,2)
Colony Ship 5: (2,2)
Colony Ship 6: (2,2)
Player 2, Final Locations:
Scout 1: (2, 2)
Scout 2: (2, 2)
Scout 3: (2, 2)
Colony Ship 4: (2,2)
Colony Ship 5: (2,2)
Colony Ship 6: (2,2)
COMBAT PHASE
Remaining ships: (list in the table below)
ATTACKING ORDER | PLAYER | SHIP | HEALTH |
---------------------------------------------------------
1 | 1 | Scout 1 | 1 |
2 | 1 | Scout 2 | 1 |
3 | 1 | Scout 3 | 1 |
4 | 2 | Scout 1 | 1 |
5 | 2 | Scout 2 | 1 |
6 | 2 | Scout 3 | 1 |
Attack 1
Attacker: Player 1 Scout 1
Defender: Player 2 Scout 1
Miss Threshold: 3
Die Roll: 1
Hit or Miss: Hit (since Die Roll <= Miss Threshold)
ATTACKING ORDER | PLAYER | SHIP | HEALTH |
-------------------------------------------------------------
1 | 1 | Scout 1 | 1 |
2 | 1 | Scout 2 | 1 |
3 | 1 | Scout 3 | 1 |
4 | 2 | Scout 2 | 1 |
5 | 2 | Scout 3 | 1 |
Attack 2
Attacker: Player 1 Scout 2
Defender: Player 2 Scout 2
Hit Threshold: 3
Die Roll: 2
Hit or Miss: Hit (since Die Roll <= Miss Threshold)
ATTACKING ORDER | PLAYER | SHIP | HEALTH |
-------------------------------------------------------------
1 | 1 | Scout 1 | 1 |
2 | 1 | Scout 2 | 1 |
3 | 1 | Scout 3 | 1 |
5 | 2 | Scout 3 | 1 |
Attack 3
Attacker: Player 1 Scout 3
Defender: Player 2 Scout 3
Hit Threshold: 3
Dice Roll: 3
Hit or Miss: Hit (since Die Roll <= Miss Threshold)
ATTACKING ORDER | PLAYER | SHIP | HEALTH |
-------------------------------------------------------------
1 | 1 | Scout 1 | 1 |
2 | 1 | Scout 2 | 1 |
3 | 1 | Scout 3 | 1 |
Combat phase complete
ECONOMIC PHASE
...
(ML; 60 min)
Create a file machine-learning/analysis/sandwich_ratings_interaction_terms.py
to store your code/answers for this problem.
The food manufacturing company (from the previous assignment) decided to gather some more data on roast beef and peanut butter sandwiches. (The new dataset has two additional rows at the bottom.)
Slices of Roast Beef | Tablespoons of Peanut Butter | Rating |
--------------------------------------------------------------
0 | 0 | 1 |
1 | 0 | 2 |
2 | 0 | 4 |
4 | 0 | 8 |
6 | 0 | 9 |
0 | 2 | 2 |
0 | 4 | 5 |
0 | 6 | 7 |
0 | 8 | 6 |
2 | 2 | 0 | (new data)
3 | 4 | 0 | (new data)
a) Using the pseudoinverse, fit another model
$$ \text{rating} = \beta_0 + \beta_1 (\text{slices beef}) + \beta_2 (\text{tbsp peanut butter}).$$Include your answers to the following questions as comments in your code.
# QUESTION 1
# What is the model?
# rating = ___ + ___ * (slices beef) + ___ * (tbsp peanut butter)
# QUESTION 2
# What is the predicted rating of a sandwich with 5 slices of roast beef AND
# 5 tablespoons of peanut butter (on the same sandwich)?
# ___
# QUESTION 3
# How does this prediction compare to that from the previous assignment? Did
# including the additional data make the prediction trustworthy? Why or why not?
# ___
b) Again using the pseudoinverse, fit a model that has an "interaction term" corresponding to $\beta_3.$
$$ \text{rating} = \beta_0 + \beta_1 (\text{slices beef}) + \beta_2 (\text{tbsp peanut butter}) + \beta_3 (\text{slices beef})(\text{tbsp peanut butter}), $$Include your answers to the following questions as comments in your code.
# QUESTION 4
# Fill out the table with the additional interaction term:
# (slices beef) | (tbsp peanut butter) | (slices beef)(tbsp peanut butter) | Rating |
# -----------------------------------------------------------------------------------
# 0 | 0 | _ | 1 |
# 1 | 0 | _ | 2 |
# 2 | 0 | _ | 4 |
# 4 | 0 | _ | 8 |
# 6 | 0 | _ | 9 |
# 0 | 2 | _ | 2 |
# 0 | 4 | _ | 5 |
# 0 | 6 | _ | 7 |
# 0 | 8 | _ | 6 |
# 2 | 2 | _ | 0 | (new data)
# 3 | 4 | _ | 0 | (new data)
# QUESTION 5
# What is the system of equations?
# _ * beta_0 + _ * beta_1 + _ * beta_2 + _ * beta_3 = _
# _ * beta_0 + _ * beta_1 + _ * beta_2 + _ * beta_3 = _
# _ * beta_0 + _ * beta_1 + _ * beta_2 + _ * beta_3 = _
# _ * beta_0 + _ * beta_1 + _ * beta_2 + _ * beta_3 = _
# _ * beta_0 + _ * beta_1 + _ * beta_2 + _ * beta_3 = _
# _ * beta_0 + _ * beta_1 + _ * beta_2 + _ * beta_3 = _
# _ * beta_0 + _ * beta_1 + _ * beta_2 + _ * beta_3 = _
# _ * beta_0 + _ * beta_1 + _ * beta_2 + _ * beta_3 = _
# _ * beta_0 + _ * beta_1 + _ * beta_2 + _ * beta_3 = _
# _ * beta_0 + _ * beta_1 + _ * beta_2 + _ * beta_3 = _
# _ * beta_0 + _ * beta_1 + _ * beta_2 + _ * beta_3 = _
# QUESTION 6
# What is the matrix equation?
# [[_, _, _, _], [[_],
# [_, _, _, _], [_],
# [_, _, _, _], [_],
# [_, _, _, _], [[beta_0], [_],
# [_, _, _, _], [beta_1], = [_],
# [_, _, _, _], [beta_2], [_],
# [_, _, _, _], [beta_3]] [_],
# [_, _, _, _], [_],
# [_, _, _, _], [_],
# [_, _, _, _], [_],
# [_, _, _, _], [_],
# [_, _, _, _]] [_]]
# QUESTION 7
# What is the model?
# rating = ___ + ___ * (slices beef) + ___ * (tbsp peanut butter) + ___ * (slices beef)(tbsp peanut butter)
# QUESTION 8
# What is the predicted rating of a sandwich with 5 slices of roast beef AND
# 5 tablespoons of peanut butter (on the same sandwich)?
# ___
# QUESTION 9
# How does this prediction compare to that from the previous assignment? Did
# including interaction term make the prediction trustworthy? Why or why not?
# ___
(Stats; 60 min)
Create a file assignment-problems/kl_divergence_for_monte_carlo_simulations.py
to store your code/answers for this problem.
The Kullback–Leibler divergence (or relative entropy) between two probability distributions $P(X)$ and $Q(X)$ is defined as
\begin{align*} \mathcal{D}(P \, || \, Q) = \sum\limits_{X} P(X) \ln \left( \dfrac{P(X)}{Q(X)} \right) \end{align*}Intuitively, the divergence measures how "different" the two distributions are.
a) Write a function kl_divergence(p, q)
that computes the KL divergence between two probability distributions p
and q
, represented as arrays. Test your function by assert
ing that it passes the following test:
>>> p = [0.2, 0.7, 0.1]
>>> q = [0.1, 0.8, 0.1]
>>> kl_divergence(p,q)
0.04515746127
[ ^ computation for the above is 0.2*ln(0.2/0.1) + 0.7*ln(0.7/0.8) + 0.1*ln(0.1/0.1) ]
b) Compute the KL divergence where p
is the Monte Carlo distribution and q
is the true distribution for the number of heads in 5 coin tosses, using 1,000 samples in your Monte Carlo simulation (that's the default number from the previous assignment).
Then do the same computation with 100 samples, and then with 10,000 samples. Print out the results for all 3 computations:
>>> python assignment-problems/kl_divergence_for_monte_carlo_simulations.py
Testing KL Divergence... Passed!
Computing KL Divergence for MC Simulations...
100 samples --> KL Divergence = ___
1,000 samples --> KL Divergence = ___
10,000 samples --> KL Divergence = ___
In a comment in your code, write down what the general trend is and why:
# As the number of samples increases, the KL divergence __________ because _______________________________.
(Space Empires; 60 min)
Create a file notes/game_events_using_combat_testing_players.txt
and fill out the following template for what will happen during the movement and combat phases in the first turn when two CombatTestingPlayers
play each other on a 5-by-5 grid.
Assume the dice rolls come out in perfect order: 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 1, 2, 3...
Note the following rules regarding attack order:
Fire combat is never simultaneous. Ships with an A Class fire before ships with a B Class, ships with a B Class fire before ships with a C, etc. If both players have groups with the same Class (for example, if both are B’s), the group belonging to the player with the higher Tactics Technology fires first. If the Class and the Tactics Technology of both groups are the same, then the defender's group fires first.
(The defender is the group that was the first to occupy the space.)
Note: You don't have to implement your tests yet. We should first make sure we're all in agreement on what the outcome of the situation should be.
STARTING CONDITIONS
Players 1 and 2
are CombatTestingPlayers
start with 20 CPs
have an initial fleet of 3 scouts and 3 colony ships
---
TURN 1
MOVEMENT PHASE
Player 1, Movement 1
Scouts 1,2,3: (2,0) --> ___
Player 2, Movement 1
Scouts 1,2,3: (2,4) --> ___
Player 1, Movement 2
Scout 1,2,3: ___ --> ___
Player 2, Movement 2
Scouts 1,2,3: ___ --> ___
Players 1/2, Movement 3
___
Player 1, Final Locations:
Scout 1: ___
Scout 2: ___
Scout 3: ___
Player 2, Final Locations:
Scout 1: ___
Scout 2: ___
Scout 3: ___
COMBAT PHASE
Remaining ships: (list in the table below)
ATTACKING ORDER | PLAYER | SHIP | HEALTH |
---------------------------------------------------------
1 | _ | _ | _ |
2 | _ | _ | _ |
3 | _ | _ | _ |
...
Attack 1
Attacker: ___
Defender: ___
Hit Threshold: ___
Dice Roll: ___
Hit or Miss: ___ <-- label as "Hit" or "Miss"
Remaining ships: (in the table, only list the ships which remain alive)
ATTACKING ORDER | PLAYER | SHIP | HEALTH |
---------------------------------------------------------
1 | _ | _ | _ |
2 | _ | _ | _ |
3 | _ | _ | _ |
...
Attack 2
Attacker: ___
Defender: ___
Hit Threshold: ___
Dice Roll: ___
Hit or Miss: ___ <-- label as "Hit" or "Miss"
Remaining ships: (in the table, only list the ships which remain alive)
ATTACKING ORDER | PLAYER | SHIP | HEALTH |
---------------------------------------------------------
1 | _ | _ | _ |
2 | _ | _ | _ |
3 | _ | _ | _ |
...
... (continue until combat is complete)
(ML; 75 min)
Create a file machine-learning/analysis/sandwich_ratings.py
to store your code/answers for this problem.
A food manufacturing company is testing out some recipes for roast beef sandwiches and peanut butter sandwiches. They fed sandwiches to several subjects, and the subjects rated the sandwiches.
Slices of Roast Beef | Tablespoons of Peanut Butter | Rating |
--------------------------------------------------------------
0 | 0 | 1 |
1 | 0 | 2 |
2 | 0 | 4 |
4 | 0 | 8 |
6 | 0 | 9 |
0 | 2 | 2 |
0 | 4 | 5 |
0 | 6 | 7 |
0 | 8 | 6 |
In this question, you will fit a plane (i.e. a linear model on 2 input variables) to the data using the following model:
$$ \text{rating} = \beta_0 + \beta_1 \times (\text{slices beef}) + \beta_2 \times (\text{tbsp peanut butter})$$a) As a comment in your code, fill in the system of $9$ linear equations that we seek to satisfy. (Each data point corresponds to a linear equation.)
# ___ * beta_0 + ___ * beta_1 + ___ * beta_2 = ___
# ___ * beta_0 + ___ * beta_1 + ___ * beta_2 = ___
# ___ * beta_0 + ___ * beta_1 + ___ * beta_2 = ___
# ___ * beta_0 + ___ * beta_1 + ___ * beta_2 = ___
# ___ * beta_0 + ___ * beta_1 + ___ * beta_2 = ___
# ___ * beta_0 + ___ * beta_1 + ___ * beta_2 = ___
# ___ * beta_0 + ___ * beta_1 + ___ * beta_2 = ___
# ___ * beta_0 + ___ * beta_1 + ___ * beta_2 = ___
# ___ * beta_0 + ___ * beta_1 + ___ * beta_2 = ___
b) As a comment in your code, fill in the corresponding matrix equation:
# [[___, ___, ___], [[___],
# [___, ___, ___], [___],
# [___, ___, ___], [___],
# [___, ___, ___], [[beta_0], [___],
# [___, ___, ___], [beta_1], = [___],
# [___, ___, ___], [beta_2]] [___],
# [___, ___, ___], [___],
# [___, ___, ___], [___],
# [___, ___, ___]] [___]]
Note: the comment above is meant to illustrate the following format:
\begin{align} \underbrace{\begin{bmatrix} \_\_ & \_\_ & \_\_ \\ \_\_ & \_\_ & \_\_ \\ \_\_ & \_\_ & \_\_ \\ \_\_ & \_\_ & \_\_ \\ \_\_ & \_\_ & \_\_ \\ \_\_ & \_\_ & \_\_ \\ \_\_ & \_\_ & \_\_ \\ \_\_ & \_\_ & \_\_ \\ \_\_ & \_\_ & \_\_ \end{bmatrix}}_{X} \underbrace{\begin{bmatrix} \_\_ \\ \_\_ \\ \_\_ \end{bmatrix}}_{\vec{\beta}} &= \underbrace{\begin{bmatrix} \_\_ \\ \_\_ \\ \_\_ \\ \_\_ \\ \_\_ \\ \_\_ \\ \_\_ \\ \_\_ \\ \_\_ \end{bmatrix}}_{\vec{y}} \end{align}
c) Use your Matrix
class to compute the least-squares approximation. Do this computation in the file machine-learning/analysis/sandwich-ratings.py
.
As a comment in your code, fill in the blanks in the resulting model:
# rating = ___ + ___ * (slices of beef) + ___ * (tbsp peanut butter)
d) Use your model to predict the rating of a sandwich with $5$ slices of roast beef and no peanut butter. Your code should print out this value, and label that it corresponds to $5$ slices of roast beef and no peanut butter.
e) Predict the rating of a sandwich with $5$ slices of roast beef AND $5$ tablespoons of peanut butter (both ingredients on the same sandwich). Your code should print out this value, and label that it corresponds to $5$ slices of roast beef and $5$ tbsp peanut butter.
Should the company trust this prediction? Why or why not? Write your answer as a comment in your code.
# ____, because __________________________________________________________
(Space Empires; 90 min)
Create the following classes:
Create a class CombatTestingPlayer
that uses the following strategy:
Destroyers
and Scouts
, starting off buying a Destroyer
. If it can't pay for the next ship, then just wait until the next turn to buy it.Create a class CombatEngine
that handles all of the combat functionality, and refactor your Game
so that whenever it enters combat phase, it gathers the units that occupy the same grid space and passes them into CombatEngine
for processing:
Game asks Board what ships are on the same grid space
Game passes ships into CombatEngine
CombatEngine asks Players what ships they want to screen
CombatEngine figures out the attacking order
while combat is occurring:
for each Unit in the attacking order:
CombatEngine asks the unit's Player what other unit they want to attack
CombatEngine executes the attack by rolling the dice and logging any hit as appropriate
Game proceeds onwards to economic phase
Make sure that when two CombatPlayers
play against each other, there are many battles taking place at the center of the board.
Also, make sure that your CombatEngine
is implemented well. I DON'T want to see anything ridiculous like CombatEngine
inheriting from Game
or Board
, or implementing any sort of strategy on its own. The Player
is the only thing that should be implementing strategy.
Next time, we'll figure out how to test CombatEngine
to ensure that the combat is progressing correctly. But this time, just focus on pulling out the Game
's combat methods and moving it into CombatEngine
.
(Stats; 30 min)
a) Using your function probability(num_heads, num_flips)
, plot the distribution for the number of heads in 10
coin flips. In other words, plot the curve $y=p(x),$ where $p(x)$ is the probability of getting $x$ heads in $10$ coin flips.
b) Make 5 more plots, each using your function monte_carlo_probability(num_heads, num_flips)
.
c) Put all your plots on the same graph, label them with a legend to indicate whether each plot is the true distribution or a monte carlo simulation, and save the figure as plot.png
.
Legend: True, MC 1, MC 2, MC 3, MC 4, MC 5.
Make the true distribution thick (linewidth=2.5
) and the monte carlo distributions thin (linewidth=0.75
). A plotting example is shown below to assist you.
import matplotlib.pyplot as plt
plt.style.use('bmh')
plt.plot([0,1,2,3,4],[0.1, 0.3, 0.5, 0.1, 0.1],linewidth=2.5)
plt.plot([0,1,2,3,4],[0.3, 0.1, 0.4, 0.2, 0.1],linewidth=0.75)
plt.plot([0,1,2,3,4],[0.2, 0.2, 0.3, 0.3, 0.2],linewidth=0.75)
plt.legend(['True','MC 1','MC 2'])
plt.xlabel('Number of Heads')
plt.ylabel('Probability')
plt.title('True Distribution vs Monte Carlo Simulations for 10 Coin Flips')
plt.savefig('plot.png')
(Algo; 90 min)
Problem Context
Suppose that a railroad network has a list of all of its segments between two towns. For example, if
railroad_segments = [('B','C'), ('B','A'), ('A','D'), ('E','D'), ('C','F'), ('G','C')]
then the towns could be arranged as
A -- B -- C -- F
\ \
D -- E G
Assume that
Problem Statement
Write a function order_towns_by_travel_time(starting_town, railroad_segments)
that lists the towns in order of travel time from the starting_town
. Implement this function using two different techniques, both of which we went over during class:
order_towns_by_travel_time_using_tree_class(starting_town, railroad_segments)
: modify the railroad_segments
edge list so that you can pass it into your Tree
class and call the breadth_first_traversal()
method.
order_towns_by_travel_time_from_scratch(starting_town, railroad_segments)
: implement breadth first search from scratch, so that it uses the railroad_segments
edge list as-is.
Where to Store / Test the Code
Create a repository graph
.
Put your Tree
class in graph/src/tree.py
.
Implement the functions order_towns_by_travel_time_using_tree_class
and order_towns_by_travel_time_from_scratch
in a file graph/analysis/railroad_travel_time.py
.
Create a testing file graph/analysis/test_railroad_travel_time.py
, and ASSERT THAT BOTH OF YOUR FUNCTIONS PASS THE FOLLOWING TESTS:
>>> railroad_segments = [('B','C'), ('B','A'), ('A','D'), ('E','D'), ('C','F'), ('G','C')]
>>> order_towns_by_travel_time('D', railroad_segments)
can return either of two possible answers:
[D, E, A, B, C, F, G]
[D, A, E, B, C, F, G]
[D, E, A, B, C, G, F]
[D, A, E, B, C, G, F]
>>> order_towns_by_travel_time('A', railroad_segments)
can return either of eight possible answers:
[A, D, B, C, E, F, G]
[A, B, D, C, E, F, G]
[A, D, B, E, C, F, G]
[A, B, D, E, C, F, G]
[A, D, B, C, E, G, F]
[A, B, D, C, E, G, F]
[A, D, B, E, C, G, F]
[A, B, D, E, C, G, F]
(SWE; 0-90 min)
Resolve ALL the remaning issues on your refactoring list. This is the third assignment on which significant time was devoted to refactoring, so I'm expecting you to have 100% of the issues resolved before the next class. Next to your name, it should say "0 issues remaining".
(Stats; 60 min)
In this problem, you will compute the probability of getting num_heads
heads in num_flips
flips of a fair coin. You will do this using two different methods. You should write your functions in a file assignment-problems/coin_flipping.py
a) Write a function probability(num_heads, num_flips)
that uses mathematics to compute the probability.
First, compute the total number of possible outcomes for num_flips
flips. (Hint: it's an exponent.)
Then, compute the number of outcomes in which num_heads
heads arise in num_flips
flips. (Hint: it's a combination.)
Then, divide the results.
b) Write a function monte_carlo_probability(num_heads, num_flips)
that uses simulation to compute the probability.
First, simulate 1000 trials of num_flips
coin flips, keeping track of how many heads there were.
Then, divide the number of outcomes in which there were num_heads
heads, by the total number of trials (1000).
c) When you run assignment-problems/coin_flipping.py
, you should print out the result of probability(5,8)
. Also, print out 3 instances of monte_carlo_probability(5,8)
.
(SWE; 120 min)
a) Resolve the remaining issues on your refactoring list.
b) In tests/test_matrix.py
, test that the inverse is correctly computed for the following matrices. To do this, you should compute the inverse, multiply the matrix by its inverse, and then check that the result (when rounded to 6 decimal places) is equal to the identity matrix.
A = [[1, 2, 3, 4],
[5, 0, 6, 0],
[0, 7, 0, 8],
[9, 0, 0, 10]]
assert round_down(A @ A.inverse()) == identity_matrix
B = [[1.2, 5.3, 8.9, -10.3, -15],
[3.14, 0, -6.28, 0, 2.71],
[0, 1, 1, 2, 3],
[5, 8, 13, 21, 34],
[1, 0, 0.5, 0, 0.1]]
assert round_down(B @ B.inverse()) == identity_matrix
(ML; 30 min)
For this problem, make a file machine-learning/analysis/rocket_takeoff_regression.py
and put your code there.
Recall the following dataset, which represents the distance between a rocket and Earth's surface, as the rocket takes off. The data points are given in the form (time, distance)
.
data = [(1, 3.1), (2, 10.17), (3, 20.93), (4, 38.71), (5, 60.91), (6, 98.87), (7, 113.92), (8, 146.95), (9, 190.09), (10, 232.65)]
a) Using your PolynomialRegression
class, fit a quadratic to the data:
According to the quadratic, what is the predicted position of the rocket after 200 seconds? Make rocket_takeoff_regression.py
print out your answer.
b) Your friend claims that a cubic model will better fit the data. So, using your PolynomialRegression
class, fit a cubic to the data:
According to the cubic, what is the predicted position of the rocket after 200 seconds? Make rocket_takeoff_regression.py
print out your answer.
c) Which model is better, the quadratic or the cubic? Justify your answer. Write your answer as a comment in rocket_takeoff_regression.py
(SWE; 180 min)
Catch up: Make sure your tests for machine-learning/test/test_matrix.py
and space-empires/src/test_dumb_player.py
are working and complete.
DumbPlayer
test is using assert statements to check the relevant aspects of the game. You should NOT just print out the logging data and compare it visually. Rather, for each test, you should print out what you're testing for and whether the test passed.Naming conventions:
Make sure all your files are named properly, using snake_case and correct grammar.
Make sure all your classes are named as nouns and all your methods/functions are named as verbs. This includes past files as well.
Refactoring: Resolve all the high-priority issues on your refactoring list.
NOTE: There's really not much else on this assignment, so I'm expecting you to get all of the above done over the weekend. If you run into any prolonged trouble, please make a post on Discord! If you find yourself not making progress on an issue for 30 min or more, just make a post for help and move onto another thing.
(DS/Algo; 60 min)
Create a testing file tests/test_matrix.py
that tests your matrix methods
1 test for each arithmetic operation: addition, subtraction, scalar multiplication, matrix multiplication, transpose.
6 tests for reduced row echelon form. You should test all combinations of the following cases:
3 tests for each of inverse, inverse by minors, determinant, and recursive determinant (so, 12 tests in all).
(DS/Algo; 45 min)
Write a recursive function divide_and_conquer_sort(x)
that sorts an input list x
according to the following technique:
If the input list consist of more than one element, then split the list into the first half and the second half, recursively use
divide_and_conquer_sort
to sort each half, combine the two sorted halves, and return the result. Otherwise, if the input list consists of only one element, then the input list is already sorted and you can return it.
Put your function in assignment-problems
and create a testing file that implements 4 tests, each of which are significantly different in some way.
HERE IS SOME PSEUDOCODE TO HELP YOU STRUCTURE YOUR FUNCTION:
divide_and_conquer_sort(input list):
if the input list consists of more than one element:
break up the input list into its left and right halves
sort the left and right halves by recursively calling divide_and_conquer_sort
combine the two sorted halves
return the result
otherwise, if the input list consists of only one element, then it is already sorted,
and you can just return it.
And here is an example of what's going on under the hood:
input list:[6,9,7,4,2,1,8,5]
break it in half: [6,9,7,4] [2,1,8,5]
use divide_and_conquer_sort recursively to sort the two halves
input list: [6,9,7,4]
break it in half: [6,9] [7,4]
use divide_and_conquer_sort recursively to sort the two halves
input list: [6,9]
break it in half: [6] [9]
the two halves have only one element each, so they are already sorted
so we can combine them to get [6,9]
input list: [7,4]
break it in half: [7] [4]
the two halves have only one element each, so they are already sorted
so we can combine them to get [4,7]
now we have two sorted lists [6,9] and [4,7]
so we can combine them to get [4,6,7,9]
input list: [2,1,8,5]
break it in half: [2,1] [8,5]
use divide_and_conquer_sort recursively to sort the two halves
input list: [2,1]
break it in half: [2] [1]
the two halves have only one element each, so they are already sorted
so we can combine them to get [1,2]
input list: [8,5]
break it in half: [8] [5]
the two halves have only one element each, so they are already sorted
so we can combine them to get [5,8]
now we have two sorted lists [1,2] and [5,8]
so we can combine them to get [1,2,5,8]
now we have two sorted lists [4,6,7,9] and [1,2,5,8]
so we can combine them to get [1,2,4,5,6,7,8,9]
(SWE; 90 min)
Write a testing file tests/test_dumb_player.py
to ensure that a game on a $5 \times 5$ grid with two DumbPlayer
s proceeds correctly.
Over the course of 4 turns, you should check the board for the following 8 tests:
At the end of Turn 1 Movement Phase:
At the end of Turn 1 Economic Phase:
At the end of Turn 2 Movement Phase:
At the end of Turn 2 Economic Phase:
At the end of Turn 3 Movement Phase:
At the end of Turn 3 Economic Phase:
At the end of Turn 4 Movement Phase:
At the end of Turn 4 Economic Phase:
For your reference, here's my scratch work when figuring out the conditions above.
STARTING CONDITIONS
Players 1 and 2
are DumbPlayers
start with 20 CPs
have an initial fleet of 3 scouts and 3 colony ships
---
TURN 1
MOVEMENT PHASE
Player 1, Movement 1
Scouts 1,2,3: (2,0) --> (3,0)
Player 2, Movement 1
Scouts 1,2,3: (2,4) --> (3,4)
Player 1, Movement 2
Scout 1,2,3: (3,0) --> (4,0)
Player 2, Movement 2
Scouts 1,2,3: (3,4) --> (4,4)
Players 1/2, Movement 3
no movements occur
Player 1, Final Locations:
Scout 1: (4,0)
Scout 2: (4,0)
Scout 3: (4,0)
Player 2, Final Locations:
Scout 1: (4,4)
Scout 2: (4,4)
Scout 3: (4,4)
COMBAT PHASE
no combats occur
ECONOMIC PHASE
Players 1/2
starting CP: 20
colony income: 3 CP/Colony x 1 Colony = 3 CP
maintenance costs: 1 CP/Scout x 3 Scouts = 3 CP
remaining CP: 20
buy scouts: 6 CP/Scout x 3 Scouts = 18 CP
remaining CP: 2
---
TURN 2
MOVEMENT PHASE
Player 1, Movement 1
Scouts 4,5,6: (2,0) --> (3,0)
Player 2, Movement 1
Scouts 4,5,6: (2,4) --> (3,4)
Player 1, Movement 2
Scout 4,5,6: (3,0) --> (4,0)
Player 2, Movement 2
Scouts 4,5,6: (3,4) --> (4,4)
Players 1/2, Movement 3
no movements occur
Player 1, Final Locations:
Scout 1: (4,0)
Scout 2: (4,0)
Scout 3: (4,0)
Scout 4: (4,0)
Scout 5: (4,0)
Scout 6: (4,0)
Player 2, Final Locations:
Scout 1: (4,4)
Scout 2: (4,4)
Scout 3: (4,4)
Scout 4: (4,4)
Scout 5: (4,4)
Scout 6: (4,4)
COMBAT PHASE
no combats occur
ECONOMIC PHASE
Players 1/2
starting CP: 2
colony income: 3 CP/Colony x 1 Colony = 3 CP
maintenance costs: 1 CP/Scout x 6 Scouts = 6 CP
unable to maintain Scout 6; Scout 6 is removed
remaining CP: 0
---
TURN 3
MOVEMENT PHASE
Players 1/2, Movements 1/2/3
no movements occur
Player 1, Final Locations:
Scout 1: (4,0)
Scout 2: (4,0)
Scout 3: (4,0)
Scout 4: (4,0)
Scout 5: (4,0)
Player 2, Final Locations:
Scout 1: (4,4)
Scout 2: (4,4)
Scout 3: (4,4)
Scout 4: (4,4)
Scout 5: (4,4)
COMBAT PHASE
no combats occur
ECONOMIC PHASE
Players 1/2
starting CP: 0
colony income: 3 CP/Colony x 1 Colony = 3 CP
maintenance costs: 1 CP/Scout x 5 Scouts = 5 CP
unable to maintain Scouts 4/5; Scouts 4/5 are removed
remaining CP: 0
---
TURN 3
MOVEMENT PHASE
Players 1/2, Movements 1/2/3
no movements occur
Player 1, Final Locations:
Scout 1: (4,0)
Scout 2: (4,0)
Scout 3: (4,0)
Player 2, Final Locations:
Scout 1: (4,4)
Scout 2: (4,4)
Scout 3: (4,4)
COMBAT PHASE
no combats occur
ECONOMIC PHASE
Players 1/2
starting CP: 0
colony income: 3 CP/Colony x 1 Colony = 3 CP
maintenance costs: 3 CP/Scout x 3 Scouts = 3 CP
remaining CP: 0
---
all future turns continue the same way as TURN 3
(ML; 30 min)
Create a file tests/test_polynomial_regressor.py
to test your PolynomialRegressor
on the following tests. (Round the comparisons to 6 decimal places.)
from polynomial_regressor import PolynomialRegressor
data = [(0,1), (1,2), (2,5), (3,10), (4,20), (5,30)]
constant_regressor = PolynomialRegressor(degree=0)
constant_regressor.ingest_data(data)
constant_regressor.solve_coefficients()
constant_regressor.coefficients
[11.333333333333332]
constant_regressor.evaluate(2)
11.333333333333332
linear_regressor = PolynomialRegressor(degree=1)
linear_regressor.ingest_data(data)
linear_regressor.solve_coefficients()
linear_regressor.coefficients
[-3.2380952380952412, 5.828571428571428]
linear_regressor.evaluate(2)
8.419047619047616
quadratic_regressor = PolynomialRegressor(degree=2)
quadratic_regressor.ingest_data(data)
quadratic_regressor.solve_coefficients()
quadratic_regressor.coefficients
[1.107142857142763, -0.6892857142856474, 1.3035714285714226]
quadratic_regressor.evaluate(2)
4.942857142857159
cubic_regressor = PolynomialRegressor(degree=3)
cubic_regressor.ingest_data(data)
cubic_regressor.solve_coefficients()
cubic_regressor.coefficients
[1.1349206349217873, -0.8161375661377197, 1.3730158730155861, -0.009259259259233155]
cubic_regressor.evaluate(2)
4.920634920634827
fifth_degree_regressor = PolynomialRegressor(degree=5)
fifth_degree_regressor.ingest_data(data)
fifth_degree_regressor.solve_coefficients()
fifth_degree_regressor.coefficients
[0.9999999917480108, -2.950000002085698, 6.9583333345161265, -3.9583333337779045, 1.0416666667658463, -0.09166666667401097]
fifth_degree_regressor.evaluate(2)
4.999999990103076
(DS/Algo; 30 min)
a) Make a new repository assignment-problems
. Going forward, this repository will hold any miscellaneous functions you write in assignments.
b) Write a function combine_sorted_lists(x,y)
that combines two sorted lists x
and y
so that the result itself is also sorted. You should build up the output list by going through x
and y
in parallel, and repeatedly taking the smallest value.
x
and y
and put them in a new list, but you shouldn't actually remove anything from x
and y
. Rather, you should just loop through each list in parallel, keeping track of your indices in x
and y
, and repeatedly bring a copy of the smallest element into the output list.>>> combine_sorted_lists([1,3,4,5,7],[2,6])
[1,2,3,4,5,6,7]
c) Put your function in a file combine_sorted_lists.py
. Then, create a file test_combine_sorted_lists.py
that runs $4$ tests on the function you wrote.
(SWE; 90 min)
a) Create a new class RandomPlayer
that inherits from Player
. Then, take any methods in Player
for which random strategy is currently used, and move them to RandomPlayer
.
move
, build_fleet
, etc.b) Create another class DumbPlayer
that has the same format as RandomPlayer
, but that uses the following strategy:
Only spend money on scouts. Always build as many scouts as possible, and don't ever buy anything else (not even technology).
Always move ships to the right.
c) Put the player class files in a folder player/
that is analogous to the unit/
folder. So, you should have
src/
|- player/
. |- player.py
. |- random_player.py
. |- dumb_player.py
d) Check to see what happens when two DumbPlayer
s play the game. You should have a bunch of scouts collect on the right.
e) Check to see what happens when a RandomPlayer
plays against a DumbPlayer
.
(Journal Club; 30 min)
Watch this video on AlphaStar, and think about the following questions. We'll discuss them in class next time, and I'm going to expect everyone to have a thought-out response to each of these questions! (Feel free to watch at higher speed, if you're able to process the information to answer the questions below.)
What is deep learning, what is reinforcement learning, and how does AlphaStar integrate them?
What are main agents and exploiter agents? How are they different, and why are exploiter agents used?
How did AlphaStar perform? Was it better than half of the human players? Better than 9 out of 10 human players? Better than that?
Update your file names to be snake case (here's an explanation of why). Also, don't use abbreviations. Write the whole name.
So, you should have:
machine-learning/
|- src/
. |- matrix.py
. |- polynomial_regressor.py
. |- gradient_descent.py
|- tests/
. |- test_gradient_descent.py
space-empires/
|- src/
. |- game.py
. |- player.py
. |- unit/
. |- unit.py
. |- scout.py
. |- battleship.py
. |- and so on...
(DS/Algo; 60 min)
Write a function cartesian_product(arrays)
that computes the Cartesian product of all the lists in arrays
.
>>> cartesian_product([['a'], [1,2,3], ['Y','Z']])
[['a',1,'Y'], ['a',1,'Z'], ['a',2,'Y'], ['a',2,'Z'], ['a',3,'Y'], ['a',3,'Z']]
NOTE: This is a reasonably short function if you use the following procedure. You'll probably have to think a bit in order to get the implementation correct, though.
Create a variable points
that will be a list of all the points in the cartesian product. Initially, set points
to consist of a single empty point: points = [[]]
.
For each array in the input, create a new list of points.
The new set of points can be constructed by looping through each existing point and, for each existing point, adding several new points.
Return the list of points.
Worked Example:
arrays = [['a'], [1,2,3], ['Y','Z']]
points: [[]]
considering array ['a']
considering element 'a'
new point ['a']
points: [['a']]
considering array [1,2,3]
considering element 1
new point ['a',1]
considering element 2
new point ['a',2]
considering element 3
new point ['a',3]
points: [['a',1], ['a',2], ['a',3]]
considering array ['Y','Z']
considering element 'Y'
new points ['a',1,'Y'], ['a',2,'Y'], ['a',3,'Y']
considering element 'Z'
new points ['a',1,'Z'], ['a',2,'Z'], ['a',3,'Z']
points: [[1,'a','Y'], [1,'a','Z'], [1,'b','Y'], [1,'b','Z'], [1,'c','Y'], [1,'c','Z']]
(ML; 60 min)
a) In GradientDescent
, clean up tests/test_gradient_descent.py
.
For each comparison of floating-point decimals, round to 10 decimal places before you check for equality.
The only things you should print are the name of each test, and if the test fails, then your custom error message. You can use the format below, or come up with your own format provided that it meets the specifications in the previous sentence.
>>> python tests/test_gradient_descent.py
Testing...
compute_gradient
single_variable_function
two_variable_function
three_variable_function
six_variable_function
descend
single_variable_function
two_variable_function
AssertionError: incorrect output for descend on three_variable_function
OUTPUT: [3.0020000000000000018, 2.0030001000000000055, 1.004000400000000004]
DESIRED: [0.0020000000000000018, -0.0030001000000000055, 0.004000400000000004]
b) In GradientDescent
, generalize your grid_search
to work on objective functions of any number of variables.
HINT: You wrote a cartesian_product
function in Problem 1, so use it here as a helper function! Just loop over the cartesian product of the input arrays.
Note: For now, don't worry about applying gradient descent within the grid search. Just find the grid point with the lowest value of the function.
Lastly, use assert
statements to write the following additional tests for your GradientDescent
class, and make sure all your tests pass. Put these additional tests in tests/test_gradient_descent.py
.
>>> def single_variable_function(x):
return (x-1)**2
>>> def two_variable_function(x, y):
return (x-1)**2 + (y-1)**3
>>> def three_variable_function(x, y, z):
return (x-1)**2 + (y-1)**3 + (z-1)**4
>>> def six_variable_function(x1, x2, x3, x4, x5, x6):
return (x1-1)**2 + (x2-1)**3 + (x3-1)**4 + x4 + 2*x5 + 3*x6
>>> minimizer = GradientDescent(single_variable_function)
>>> minimizer.grid_search([[0, 0.25, 0.75]])
>>> minimizer.minimum
[0.75]
>>> minimizer = GradientDescent(two_variable_function)
>>> minimizer.grid_search([[0, 0.25, 0.75], [0.9, 1, 1.1]])
>>> minimizer.minimum
[0.75, 0.9]
>>> minimizer = GradientDescent(three_variable_function)
>>> minimizer.grid_search([[0, 0.25, 0.75], [0.9, 1, 1.1], [0, 1, 2, 3]])
>>> minimizer.minimum
[0.75, 0.9, 1]
>>> minimizer = GradientDescent(six_variable_function)
>>> minimizer.grid_search([[0, 0.25, 0.75], [0.9, 1, 1.1], [0, 1, 2, 3],
[-2, -1, 0, 1, 2], [-2, -1, 0, 1, 2], [-2, -1, 0, 1, 2]])
>>> minimizer.minimum
[0.75, 0.9, 1, -2, -2, -2]
(SWE; 30 min)
If you haven't already, create a class Board
that stores the coordinates of all the units.
During combat, the Game
should check the Board
to identify which units occupy the same spot.
The Game
should not store its dimensions as an attribute. Rather, it should store a reference to the Board
, and the Board
should store its dimensions as an attribute.
Game.dimensions
. Rather, you would have Game.board.dimensions
.(ML; 30-90 min)
Notice that we can test code and print out custom error messages using assert
statements:
should_be_zero = 42
assert should_be_zero == 0, 'should_be_zero is NOT zero'
should_be_one = 1
assert should_be_one == 1, 'should_be_one is NOT one'
def add_numbers(x,y):
return x - y
# let's test the function above to see if it works right
test_function = add_numbers
tests = [
{'args':(0, 0), 'output': 0}, # will pass
{'args':(1, 0), 'output': 1}, # will pass
{'args':(0, 1), 'output': 1}, # will not pass; output will be -1
{'args':(1, 1), 'output': 2}
]
for test in tests:
actual_output = test_function(*test['args'])
desired_output = test['output']
error_message = 'incorrect output for {}'.format(
test_function.__name__
)
details = '\nINPUT: {}\nOUTPUT: {}\nDESIRED: {}'.format(
test['args'], actual_output, desired_output
)
assert actual_output == desired_output, error_message + details
Extend your machine-learning
library to include the following 8 tests for GradientDescent.py
. For each function, test that GradientDescent
computes the correct gradient, and that the minimum is correct after descending once along the gradient.
>>> def single_variable_function(x):
return (x-1)**2
>>> def two_variable_function(x, y):
return (x-1)**2 + (y-1)**3
>>> def three_variable_function(x, y, z):
return (x-1)**2 + (y-1)**3 + (z-1)**4
>>> def six_variable_function(x1, x2, x3, x4, x5, x6):
return (x1-1)**2 + (x2-1)**3 + (x3-1)**4 + x4 + 2*x5 + 3*x6
>>> minimizer = GradientDescent(single_variable_function)
>>> minimizer.compute_gradient(delta=0.01)
[-2.0000000000000018]
>>> minimizer.descend(scaling_factor=0.001, delta=0.01, num_steps=1)
>>> minimizer.minimum
[0.0020000000000000018]
>>> minimizer = GradientDescent(two_variable_function)
>>> minimizer.compute_gradient(delta=0.01)
[-2.0000000000000018, 3.0001000000000055]
>>> minimizer.descend(scaling_factor=0.001, delta=0.01, num_steps=1)
>>> minimizer.minimum
[0.0020000000000000018, -0.0030001000000000055]
>>> minimizer = GradientDescent(three_variable_function)
>>> minimizer.compute_gradient(delta=0.01)
[-2.0000000000000018, 3.0001000000000055, -4.0004000000000035]
>>> minimizer.descend(scaling_factor=0.001, delta=0.01, num_steps=1)
>>> minimizer.minimum
[0.0020000000000000018, -0.0030001000000000055, 0.004000400000000004]
>>> minimizer = GradientDescent(six_variable_function)
>>> minimizer.compute_gradient(delta=0.01)
[-2.0000000000000018, 3.0001000000000055, -4.0004000000000035, 1.0000000000000009, 2.0000000000000018, 3.0000000000000027]
>>> minimizer.descend(scaling_factor=0.001, delta=0.01, num_steps=1)
>>> minimizer.minimum
(0.0020000000000000018, -0.0030001000000000055, 0.004000400000000004, -0.0010000000000000009, -0.0020000000000000018, -0.0030000000000000027)
You should be able to execute the tests by running tests/test_GradientDescent.py
. MAKE SURE THAT ALL YOUR TESTS PASS, and make sure to push your finished code to Github for safekeeping.
machine-learning/
|- src/
. |- Matrix.py
. |- PolynomialRegressor.py
. |- GradientDescent.py
|- tests/
. |- test_GradientDescent.py
(SWE; 60 min)
Refactor your space-empires
library so that each movement phase consists of three movements. (No combat occurs during movement phase.)
Speed Technology
with Movement Technology
, under the following settings:Movement Technology Level | Additional CP Cost | Benefit
---------------------------------------------------------
1 | at start | Can move one space per movement
2 | 20 | Can move 1 space in each of the first 2 movements and 2 spaces in the third movement
3 | 30 | Can move 1 space in the first movement and 2 spaces in each of the second and third movements
4 | 40 | Can move 2 spaces per movement
5 | 40 | Can move 2 spaces in each of the first 2 movements and 3 spaces in the third movement
6 | 40 | Can move 2 spaces in the first movement and 3 spaces in each of the second and third movements
Note the following answers to some Space Empires questions that were asked recently:
There are no maintenance costs for Colony Ships, Bases
If the colony is destroyed are the shipyards on it destroyed too? Yes.
You cannot place multiple colonies on the same planet.
If you haven't already, put indents and blank lines in your game logging so that it's easier to read. Example:
-------------------------------------------------
TURN 12 - MOVEMENT PHASE
Player 1 - Move 1
Unit 1 (Scout) moves from (1,2) to (2,2)
Unit 1 (Battleship) moves from (3,2) to (4,2)
Player 2 - Move 1
Unit 1 (Scout) moves from (3,4) to (2,4)
Player 1 - Move 2
Unit 1 (Scout) moves from (2,2) to (3,2)
Unit 1 (Battleship) moves from (4,2) to (4,1)
Player 2 - Move 2
Unit 1 (Scout) moves from (2,4) to (1,4)
...
-------------------------------------------------
Make sure to push your finished code to Github for safekeeping.
(30 min)
machine-learning
.src
and paste your classes into files:Matrix
into Matrix.py
PolynomialRegressor
into PolynomialRegressor.py
GradientDescent
into GradientDescent.py
Commit and push the repository to your Github. The repository should have the following structure:
machine-learning/
|- src/
. |- Matrix.py
. |- PolynomialRegressor.py
. |- GradientDescent.py
Create another Github repository space-empires
and paste each class into its own file within a folder src
. This repository should have the following structure:
space-empires/
|- src/
. |- Game.py
. |- Player.py
. |- Unit/
. |- Unit.py
. |- Scout.py
. |- Battleship.py
. |- and so on...
(ML; 60 min)
Make sure that your GradientDescent
class works with functions of any number of arguments.
Tip: if you have a function
f(x,y,z)
and a listargs = [0,5,3]
, then you can passf(*args)
to evaluatef(0,5,3)
.Here's Riley's method for detecting the number of arguments to a function
f
:self.num_vars = len(inspect.getfullargspec(f).args)
Likewise, here's Elijah's method:
self.num_vars = f.__code__.co_argcount
Here is how to clone your machine-learning
repository and import your GradientDescent
:
>>> !git clone https://github.com/yourUserName/machine-learning.git
>>> import sys
>>> sys.path.append("/content/machine-learning/src/")
>>> from GradientDescent import GradientDescent
Here are some tests:
>>> def single_variable_function(x):
return (x-1)**2
>>> def two_variable_function(x, y):
return (x-1)**2 + (y-1)**3
>>> def three_variable_function(x, y, z):
return (x-1)**2 + (y-1)**3 + (z-1)**4
>>> def six_variable_function(x1, x2, x3, x4, x5, x6):
return (x1-1)**2 + (x2-1)**3 + (x3-1)**4 + x4 + 2*x5 + 3*x6
>>> minimizer = GradientDescent(single_variable_function)
>>> minimizer.minimum
(0)
>>> minimizer.compute_gradient(delta=0.01)
[-2.0000000000000018]
>>> minimizer.descend(scaling_factor=0.001, delta=0.01, num_steps=1, logging=True)
(0.0020000000000000018)
>>> minimizer = GradientDescent(two_variable_function)
>>> minimizer.minimum
(0, 0)
>>> minimizer.compute_gradient(delta=0.01)
[-2.0000000000000018, 3.0001000000000055]
>>> minimizer.descend(scaling_factor=0.001, delta=0.01, num_steps=1, logging=True)
(0.0020000000000000018, -0.0030001000000000055)
>>> minimizer = GradientDescent(three_variable_function)
>>> minimizer.minimum
(0, 0, 0)
>>> minimizer.compute_gradient(delta=0.01)
[-2.0000000000000018, 3.0001000000000055, -4.0004000000000035]
>>> minimizer.descend(scaling_factor=0.001, delta=0.01, num_steps=1, logging=True)
(0.0020000000000000018, -0.0030001000000000055, 0.004000400000000004)
>>> minimizer = GradientDescent(six_variable_function)
>>> minimizer.minimum
(0, 0, 0, 0, 0, 0)
>>> minimizer.compute_gradient(delta=0.01)
[-2.0000000000000018, 3.0001000000000055, -4.0004000000000035, 1.0000000000000009, 2.0000000000000018, 3.0000000000000027]
>>> minimizer.descend(scaling_factor=0.001, delta=0.01, num_steps=1, logging=True)
(0.0020000000000000018, -0.0030001000000000055, 0.004000400000000004, -0.0010000000000000009, -0.0020000000000000018, -0.0030000000000000027)
(SWE; 60 min)
On repl.it, refactor your game so that each turn consists of three phases: movement, combat, economic.
You should have a separate method for each of these phases:
Game.complete_movement_phase
Game.complete_combat_phase
Game.complete_economic_phase
Then, push your new changes to your space-empires
repository, clone it here, and run your game to show the output below.
(ML; 90 min)
a) Write a class GradientDescent
which organizes your gradient descent and grid search functionality. Your output should match the tests below exactly.
>>> def f(x,y):
return 1 + (x-1)**2 + (y+5)**2
>>> minimizer = GradientDescent(f)
>>> minimizer.minimum
(0, 0) # this is the default guess
>>> minimizer.grid_search([-4,-2,0,-2,4],[-4,-2,0,-2,4])
# evaluates the function on all parameter combinations and updates the minimum accordingly
>>> minimizer.minimum
(0, -4)
>>> minimizer.compute_gradient(delta=0.01)
[-2.0000000000000018, 1.9999999999999574]
>>> minimizer.descend(scaling_factor=0.001, delta=0.01, num_steps=4, logging=True)
(0.0020000000000000018, -4.002)
(0.0039959999999999996, -4.003996)
(0.005988007999999989, -4.005988008)
(0.007976031983999987, -4.007976031984)
>>> minimizer.minimum
(0.007976031983999987, -4.007976031984)
b) Make sure the test below works, using your GradientDescent
class above. Your output should match the tests below exactly.
>>> data = [(0,1), (1,2), (2,4), (3,10)]
>>> def sum_squared_error(beta_0, beta_1, beta_2):
squared_errors = []
for (x,y) in data:
estimation = beta_0 + beta_1*x + beta_2*(x**2)
error = estimation - y
squared_errors.append(error**2)
return sum(squared_errors)
>>> minimizer = GradientDescent(sum_squared_error)
>>> minimizer.descend(scaling_factor=0.001, delta=0.01, num_steps=100, logging=True)
(0.03399999999999892, 0.0799999999999983, 0.21599999999999966)
(0.06071999999999847, 0.1417999999999985, 0.3829519999999995)
(0.08180998399999845, 0.18952841599999815, 0.5119836479999996)
(0.09854562099199829, 0.22637707788799802, 0.6116981274880002)
(0.11191318351974236, 0.2548137070760939, 0.6887468675046403)
...
(0.3047314235908722, 0.32259730399636893, 0.9402940523204946)
>>> mimimizer.minimum
(0.3047314235908722, 0.32259730399636893, 0.9402940523204946)
>>> sum_squared_error(minimizer.minimum)
1.246149882168838
c) Write a class PolynomialRegressor
which organizes your polynomial regression functionality. Your output should match the tests below exactly.
>>> quadratic_regressor = PolynomialRegressor(degree=2)
>>> quadratic_regressor.coefficients
[0, 0, 0] # default coefficients --> model is 0 + 0x + 0x^2
>>> quadratic_regressor.evaluate(5)
0 # because it's 0 + 0*5 + 0*5^2
>>> data = [(0,1), (1,2), (2,4), (3,10)]
>>> quadratic_regressor.ingest_data(data)
>>> quadratic_regressor.data
[(0,1), (1,2), (2,4), (3,10)]
>>> quadratic_regressor.sum_squared_error()
121 # the predictions are all 0, so the error is 1^2 + 2^2 + 4^2 + 10^2
>>> quadratic_regressor.solve_coefficients()
>>> quadratic_regressor.coefficients
[1.1499999999999986, -0.8499999999999943, 1.249999999999993] # the coefficients calculated USING THE PSEUDOINVERSE
>>> quadratic_regressor.sum_squared_error()
0.45
>>> quadratic_regressor.plot()
(should show a plot of the regression
function along with the data)
(DS/Algo; 30 min)
Write a function heap_sort(arr)
that sorts the array by first heapifying the array, and then repeatedly popping off the root and then efficiently restoring the heap.
To efficiently restore the heap, you should NOT make another call to heapify
, because at least half of the heap is perfectly intact. Rather, you should create a helper function that implements the procedure below. (read more here)
Replace the root of the heap with last element of the heap.
Compare the element with its new children. If the element is indeed greater than or equal to its children, stop.
If not, swap the element with the appropriate child and repeat step 2.
(SWE; 60 min)
Extend your game.
Hull Size. Ships have the following hull sizes:
hull_size = 1
hull_size = 2
hull_size = 3
Change your "maintenance cost" logic so that maintenance cost is equal to hull size. Also, change your ship yard technology logic to refer to hull size, rather than armor.
Level | CP Cost | Hull Size Building Capacity of Each Ship Yard
------------------------------------------------------------
1 | - | 1
2 | 20 | 1.5
3 | 30 | 2
Ship Size Technology. In order to build particular ships, a player must have particular ship size technology.
Technology | Cost | Benefit
----------------------------------------------------------------------------
Ship Size 1 | at start | Can build Scout, Colony Ship, Ship Yard, Decoy
Ship Size 2 | 10 CPs | Can build Destroyer, Base
Ship Size 3 | 15 more CPs | Can build Cruiser
Ship Size 4 | 20 more CPs | Can build Battlecruiser
Ship Size 5 | 25 more CPs | Can build Battleship
Ship Size 6 | 30 more CPs | Can build Dreadnaught
Bases. Bases are a type of unit that can only be built on colonies (and cannot move). Bases have
Bases do not incur maintenance costs. Players must have ship size technology of 2 or more to build a base, and bases are automatically upgraded to the highest technology for free.
Decoys. Decoys are units that are inexpensive and can be used to "trick" the opponent into thinking there is an enemy ship. Decoys cost 1 CP, have zero armor, and do not incur maintenance costs. However, they are removed immediately whenever they are involved in combat (i.e. they are immediately destroyed without even being considered in the attacking order).
Combat screening. During combat, the player with the greater number of ships has the option to "screen" some of those ships, i.e. leave them out of combat. Screened ships do not participate during combat (they cannot attack or be attacked) but they remain alive after combat.
During combat a player has N ships and another player has K ships, where N > K, then the player with N ships can screen up to N-K of their ships. In other words, the player with more ships in combat can screen as many ships as they want, provided that they still have at least as many ships as their opponent participating in combat.
(ML; 90 min)
a) The following dataset represents the distance between a rocket and Earth's surface, as the rocket takes off. The data points are given in the form (time, distance)
.
[(1, 3.1), (2, 10.17), (3, 20.93), (4, 38.71), (5, 60.91), (6, 98.87), (7, 113.92), (8, 146.95), (9, 190.09), (10, 232.65)]
Use gradient descent and grid search to find the best parameters $\beta_0,$ $\beta_1,$ and $\beta_2$ that fit a quadratic function $d=\beta_0 + \beta_1 t + \beta_2 t^2$ to the data.
For the grid search, search over all odd integer combinations $(\beta_0, \beta_1, \beta_2)$ in the space $[-5,5] \times [-5,5] \times [-5,5],$ cutting off the gradient descent after a set number of iterations (max_num_iterations=50
) and returning the initial guess that had the lowest error.
If you find that the grid search is taking too long to run, you can lower max_num_iterations
.
Once you finish the grid search, continue running gradient descent on the best initial guess, to a precision of precision=0.0001
b) Using the initial guess that yielded the best-fit parameters, plot the quadratic approximation at each iteration. You can use the following function to assist with plotting.
import matplotlib.pyplot as plt
plt.style.use('bmh')
def plot_approximation(approximation_function, data, title=None, padding=5, num_subintervals=20):
x_coords_data = [point[0] for point in data]
y_coords_data = [point[1] for point in data]
x_min_data, x_max_data = min(x_coords_data), max(x_coords_data)
y_min_data, y_max_data = min(y_coords_data), max(y_coords_data)
a, b = x_min_data-padding, x_max_data+padding
approximation_x_coords = [a + (b-a)*(i/num_subintervals) for i in range(num_subintervals+1)]
approximation_y_coords = [approximation_function(x) for x in approximation_x_coords]
plt.scatter(x_coords_data, y_coords_data, color='black')
plt.plot(approximation_x_coords, approximation_y_coords, color='blue')
plt.xlim(x_min_data-padding, x_max_data+padding)
plt.ylim(y_min_data-padding, y_max_data+padding)
plt.title(title)
plt.show()
data = [(1,4), (2,5), (3,7)]
beta_0_sequence = [4 * (1-1/n+1/20)**5 for n in range(1,21)]
beta_1_sequence = [-0.5 * (1-1/n+1/20)**5 for n in range(1,21)]
beta_2_sequence = [0.5 * (1-1/n+1/20)**5 for n in range(1,21)]
for beta_0, beta_1, beta_2 in zip(beta_0_sequence, beta_1_sequence, beta_2_sequence):
title = 'y = {} + {}x + {}x^2'.format(round(beta_0,2), round(beta_1,2), round(beta_2,2))
def f(x):
return beta_0 + beta_1 * x + beta_2 * x**2
plot_approximation(f, data, title=title)
c) Verify your best-fit quadratic coefficients using the linear approximation solver you implemented with your matrix class.
d) After 200 seconds, what will be the position of the rocket according to the quadratic approximation?
(DS/Algo; 30 min)
a) Write the following functions:
get_parent_index(i)
- given the index of a node in a binary tree, returns the index of the parent of the node.
get_child_indices(i)
- given the index of a node in a binary tree, returns the indices of the children of the node.
A binary tree is indexed as follows:
0
/ \
1 2
/ \ / \
3 4 5 6
/| /|
7 8 9 10
i | get_parent_index(i) | get_child_indices(i) |
------------------------------------------------
0 | - | 1, 2 |
1 | 0 | 3, 4 |
2 | 0 | 5, 6 |
3 | 1 | 7, 8 |
4 | 1 | 9, 10 |
5 | 2 | - |
6 | 2 | - |
7 | 3 | - |
8 | 3 | - |
9 | 4 | - |
10| 4 | - |
b) Write a function heapify(arr)
that rearranges an input list so that the list satisfies the following condition:
(A binary tree satisfying the above criterion is called a max-heap.)
HINT: loop through the list, starting at the end. For each value, if the value is greater then the value of its parent in the corresponding binary tree, then swap the two values.
>>> arr = [2, 3, 7, 1, 8, 5, 6]
>>> heapified_arr = heapify(arr)
>>> print(heapified_arr)
[8, 3, 7, 1, 2, 5, 6]
The above array can be interpreted as the following tree:
8
/ \
3 7
/| |\
1 2 5 6
(SWE; 60 min)
Implement a unit ShipYard
which has
Players can only build ships at ship yards that have landed on planets that they have colonized. Initially, a player starts with 4 ship yards on their home planet.
There are some constraints on the types of ships that players can build at a shipyard. A ship with a particular level of armor can only be built at a shipyard if the number of shipyards on the planet is greater than or equal to that armor level.
A player starts with Ship Yard Technology 1 and may purchase additional ship yard technology to increase the armor building capacity of each ship yard:
Level | CP Cost | Armor Building Capacity of Each Ship Yard
------------------------------------------------------------
1 | - | 1
2 | 20 | 1.5
3 | 30 | 2
For example, if a player has a single ship yard on a planet (with ship yard technology 1), then then can only build a scout or destroyer there (both armor=1). To build a cruiser (armor=2), the player would have to either put another ship yard on the planet, or upgrade the ship yard technology to level 3.
(ML; 60 min)
a) In your function calc_linear_approximation_coefficients(data, initial_guess)
, include an optional argument plotting=False
. If set to True
, then show a plot of the data along with the linear approximation at each iteration of the gradient descent. A plotting function is provided for you, below.
Try it on each of the following datasets:
data1 = [(0, -2.7), (1, -0.01), (2, 3.28), (3, 7.07), (4, 10.99), (5, 13.51), (6, 14.75), (7, 17.93), (8, 21.65), (9, 25.48)]
data2 = [(0, 0.41), (1, 1.37), (2, 6.41), (3, 14.49), (4, 18.24), (5, 35.24), (6, 38.84), (7, 63.0), (8, 73.97), (9, 96.11)]
data3 = [(0, 0.12), (1, 4.32), (2, 5.41), (3, 0.74), (4, -3.29), (5, -4.16), (6, -1.38), (7, 3.77), (8, 5.65), (9, 2.7)]
import matplotlib.pyplot as plt
plt.style.use('bmh')
def plot_linear_approximation(beta_0, beta_1, data, title=None, padding=5):
x_coords_data = [point[0] for point in data]
y_coords_data = [point[1] for point in data]
x_min_data, x_max_data = min(x_coords_data), max(x_coords_data)
y_min_data, y_max_data = min(y_coords_data), max(y_coords_data)
line_endpoints_x_coords = [x_min_data-padding, x_max_data+padding]
line_endpoints_y_coords = [beta_0 + beta_1 * x for x in line_endpoints_x_coords]
plt.scatter(x_coords_data, y_coords_data, color='black')
plt.plot(line_endpoints_x_coords, line_endpoints_y_coords, color='blue')
plt.xlim(x_min_data-padding, x_max_data+padding)
plt.ylim(y_min_data-padding, y_max_data+padding)
plt.title(title)
plt.show()
data = [(1,4), (2,5), (3,7)]
beta_0_sequence = [2.33 * (1-1/n+1/20)**5 for n in range(1,21)]
beta_1_sequence = [1.5 * (1-1/n+1/20)**5 for n in range(1,21)]
for beta_0, beta_1 in zip(beta_0_sequence, beta_1_sequence):
title = 'y = {} + {}x'.format(round(beta_0,2), round(beta_1,2))
plot_linear_approximation(beta_0, beta_1, data, title=title)
b) Create a function calc_best_linear_approximation_coefficients(data)
that evaluates calc_linear_approximation_coefficients(data, initial_guess)
using "grid search" on ~100
initial guesses, where beta_0
and beta_1
cover all combinations of even numbers between -10
and 10
. Use precision=0.01
so that each linear approximation runs in less than a second. Then, return the coefficients that yielded the lowest error.
c) Suppose that
i) Using calc_best_linear_approximation_coefficients
, what is the linear approximation for the data?
Your answer here
ii) Assuming the relationship between playing hours and skill level is linear, if Justin plays Space Empires for 30 hours, approximately what skill level will he reach? Use the linear approximation from part (i).
Your answer here
(DS/Algo; 60 min)
Extend your Tree
class.
a) Write a method insert(new_tree, parent_node_value)
that takes a new_tree
instance and appends it onto the chosen parent_node
in self
.
parent_node_value
. Then, set the new tree's root as the parent node's child.>>> tree_A = Tree()
>>> edges_A = [('a','c'), ('e','g'), ('e','i'), ('e','a')]
>>> tree_A.build_from_edges(edges_A)
Note: at this point, the tree's internal state should look as follows
e
/|\
a i g
/
c
>>> edges_B = [('d','b'), ('a','d'), ('d','f'), ('f','h'), ('d','j'), ('d','k')]
>>> tree_B = Tree()
>>> tree_B.build_from_edges(edges_B)
The tree's internal state should look as follows:
d
/|\\
b j fk
|
h
>>> tree_A.insert(tree_B, 'a')
The tree's internal state should look as follows:
e
/|\
a i g
/|
c d
/|\\
b j fk
|
h
>>> tree_A.print_depth_first()
e a c d b j f h k i g
(other answers are permissible, e.g.
e i g a d f h b j k c)
b) Write a method print_breadth_first()
that prints the node values of a tree, breadth-first.
>>> tree = Tree()
>>> edges = [('a','c'), ('e','g'), ('e','i'), ('e','a'), ('d','b'), ('a','d'), ('d','f'), ('f','h'), ('d','j'), ('d','k')]
>>> tree.build_from_edges(edges)
The tree's internal state should look as follows:
e
/|\
a i g
/|
c d
/|\\
b j fk
|
h
>>> tree.print_breadth_first()
e a i g c d b j f k h
other answers are permissible, e.g.
e i a g c d b k j f h
(SWE; 60 min)
PART A
Make sure that your combat resolution is implemented correctly (per the previous assignment).
Example: Suppose Player A's units (A1 and A2) and Player B's units (B1 and B2) all occupy the same space on the grid, and suppose the attack classes of the units mandate the order A1 > B1 > B2 > A2. Then
Note: if two units have the same attack class, then just order them randomly in the attacking sequence.
PART B
Incorporate planets, colony ships, and colonies into your game. Players no longer receive an allowance of CPs from the game. Rather, each turn they get CPs from each of their colonies a planet.
Some facts about colony ships:
Colony ships can only move 1 space per turn, regardless of speed technology. They have attack 0 and defense 0, and are immediately destroyed whenever an enemy is present. They cost 8 CP to build.
When a colony ship lands on an uncolonized planet, it can colonize the planet to form a colony. After colonizing the planet, the colony ship can no longer move.
Some facts about colonies:
Colonies cannot attack, and they have defense strength of 0. They have no maintenance cost.
Colonies start out with a CP capacity of 3, meaning that when the colony ship has colonized a planet, it provides its player with 3 CP per turn. Note that players gain CPs only from colony ships that have colonized a planet.
Each time a colony is hit during battle, its CP production decreases by 1 (so the player gets 1 fewer CP per turn). When CP production reaches 0, the colony is destroyed.
How the game initially starts:
At each player's starting position, they have a colony ship that has colonized their home planet (at the middle-top or middle-bottom of the grid). They also are given 20 CPs, 3 scouts, and 3 additional colony ships.
8 additional planets are randomly placed throughout the 7-by-7 grid.
(DS/Algo; 60 min)
Rename your BinaryTree
class to Tree
and remove the append
method and the get_next_node_across_layer
method. Now that we've implemented children
as a list, we're going to stop working under the binary assumption.
a) Ensure that your method build_from_edges
works in general -- not just for binary trees.
while
loop that loops through the tree layers until finished.edges
and then set the root's children accordingly.edges
and then set the children of the root's children accordingly.edges
and then set the children of the third layer accordingly.b) Create a method print_depth_first(node=self.root)
that prints the values of the nodes of the tree, proceeding in the "depth-first" order.
>>> tree = Tree()
>>> edges = [('a','c'), ('e','g'), ('e','i'), ('e','a'), ('d','b'), ('a','d'), ('d','f'), ('f','h'), ('d','j'), ('d','k')]
>>> tree.build_from_edges(edges)
The tree's internal state should look as follows:
e
/|\
a i g
/|
c d
/|\\
b j fk
|
h
>>> tree_A.print_depth_first()
e a c d b j f h k i g
Other answers are permissible, as long as they
follow a depth-first ordering, e.g.
e i g a d f h b j k c
DON'T FORGET TO DO PART (C) BELOW
c) What is the time complexity of print_depth_first
? Provide justification for your answers.
(ML; 60 min)
Create a function calc_linear_approximation_coefficients(data)
that uses 2-dimensional gradient descent to compute the line of best fit $\hat{y}=\beta_0 + \beta_1 x$ for a dataset $\left\lbrace (x_1, y_1), (x_2, y_2), \ldots, (x_n, y_n) \right\rbrace.$
To do this, you will need to define the following sum_squared_error
function that computes how inaccurate the approximation is:
The sum_squared_error
function is really just a 2-variable function, with variables $\beta_0$ and $\beta_1.$ So, you can use your existing gradient_descent
function to find the values of $\beta_0$ and $\beta_1$ that minimize the sum_squared_error
.
>>> on_a_line_data = [(0,1), (1,3), (2,5), (3,7)]
>>> calc_linear_approximation_coefficients(on_a_line_data)
[1, 2]
>>> not_on_a_line_data = [(1,4), (2,5), (3,7)]
>>> calc_linear_approximation_coefficients(not_on_a_line_data)
[2.3333333, 1.5]
(SWE; 60 min)
Extend your game system.
a) Implement a "maintenance cost" for each unit, that is equal to the unit's armor
value plus its defense technology
level. On each turn, the player must pay the maintenance cost associated with each unit. If a player is unable to pay the maintenance cost for the unit, then the unit is eliminated.
armor + defense technology = 3
, so the player must pay 3 CP
per turn in order to keep the Cruiser.b) Modify your combat resolution. Instead of resolving each pair combat independently, you should repeatedly loop through all of the units (in the order of their attack class), so that every non-eliminated unit gets the chance to attack before any unit attacks twice.
Example: Suppose Player A's units (A1 and A2) and Player B's units (B1 and B2) all occupy the same space on the grid, and suppose the attack classes of the units mandate the order A1 > B1 > B2 > A2. Then
Note: if two units have the same attack class, then just order them randomly in the attacking sequence.
(DS/Algo; 60 min)
Extend your BinaryTree
class. Implement a method build_from_edges
that builds a binary tree given a list of edges (parent, child)
.
Hint: identify the root and then recursively add on child nodes.
>>> tree = BinaryTree()
>>> edges = [('a','c'), ('d','b'), ('a','d'), ('d','f'), ('e','g'), ('f','h'), ('e','a')]
>>> tree.build_from_edges(edges)
Note: at this point, the tree's internal state should look as follows
e
/ \
a g
/|
c d
/ \
b f
|
h
(ML; 60 min)
a) 2-dimensional linear regression involves finding the line of best fit $y=\beta_0 + \beta_1 x$ for a dataset $\left\lbrace (x_1, y_1), (x_2, y_2), \ldots, (x_n, y_n) \right\rbrace.$
In other words, we aim to find the values of $\beta_0$ and $\beta_1$ that most closely approximate
\begin{align*} \begin{bmatrix} 1 & x_1 \\ 1 & x_2 \\ \vdots & \vdots \\ 1 & x_n \end{bmatrix} \begin{bmatrix} \beta_0 \\ \beta_1 \end{bmatrix} &\approx \begin{bmatrix} y_1 \\ y_2 \\ \vdots \\ y_n \end{bmatrix} \\ \mathbf{X} \vec{\beta} &\approx \vec{y} \end{align*}Unfortunately, since $\mathbf{X}$ is taller than it is wide, it is not invertible. However, $\mathbf{X}^T \mathbf{X}$ is invertible, so we have
\begin{align*} \mathbf{X} \vec{\beta} &\approx \vec{y} \\ \mathbf{X}^T \mathbf{X} \vec{\beta} &\approx \mathbf{X}^T \vec{y} \\ \vec{\beta} &\approx \left( \mathbf{X}^T \mathbf{X} \right)^{-1} \mathbf{X}^T \vec{y} \end{align*}Write a function calc_linear_approximation_coefficients(data)
where data
is a list of points. Use your Matrix
class within the computation.
>>> on_a_line_data = [(0,1), (1,3), (2,5), (3,7)]
>>> calc_linear_approximation_coefficients(not_on_a_line_data)
[1, 2]
>>> not_on_a_line_data = [(1,4), (2,5), (3,7)]
>>> calc_linear_approximation_coefficients(not_on_a_line_data)
[2.3333333, 1.5]
b) Extend your recursive function
gradient_descent(f,x0,alpha=0.01,delta=0.001,precision=0.0001)
to a new function
gradient_descent(f,x0,y0,alpha=0.01,delta=0.001,precision=0.0001)
that works on 2-variable functions.
To recursively update your guesses, use the following update:
\begin{align*} x_{n+1} &= x_n - \alpha f_x(x_n, y_n) \\ y_{n+1} &= y_n - \alpha f_y(x_n, y_n) \end{align*}Note that you will need to write a helper function that computes partial derivatives using an unbiased (centered) difference quotient:
\begin{align*} f_x(x_n, y_n) &\approx \dfrac{f(x_n+\delta, y_n) - f(x_n-\delta, y_n)}{2\delta} \\ f_y(x_n, y_n) &\approx \dfrac{f(x_n, y_n+\delta) - f(x_n, y_n-\delta)}{2\delta} \\ \end{align*}where $\delta$ (delta) is chosen as a very small constant. (For our cases, $\delta = 0.001$ should be sufficient.)
The function should recurse until successive guesses are within precision
amount of each other.
a) Use your gradient descent function to minimize $f(x,y)=1+x^2+y^2$ starting with the initial guess $(1,2).$
b) Use your gradient descent function to minimize $f(x,y) = 3 + (2x-5)^2 + (3y+1)^2.$
(SWE; 60 min)
Extend your game system.
On each turn, randomly choose to either upgrade technology or build more ships.
Implement speed technology. It increases speed in the same way that attack technology increases attack and defense technology increases defense.
Speed Technology Level | Additional CP Cost |
---------------------------------------------
0 | - |
1 | 90 |
2 | 120 |
Modify your Scout class so that its default speed is 1
.
Make it so that a unit's technology level is equal to the technology that existed at the time of building the unit.
(Algorithms / Data Structures; 60 min)
Extend your BinaryTree
class.
Get rid of the Node
attributes right
and left
. Instead, store an attribute children
that is a list of 0, 1, or 2 child nodes. Make sure your append
method still works.
For example, if you previously had a node with Node.left = A
and Node.right = B
, you should now have Node.children = [A, B]
. Or, if you had Node.left = None
and Node.right = B
, then you should now have Node.children = [B]
.
# This is the code we wrote during class.
# You can use it as a starting point if you'd like.
class BinaryTree:
def __init__(self, head_value):
self.root = Node(head_value)
def append(self, value):
current_node = self.root
while current_node.children_are_filled():
current_node = current_node.get_next_node_across_layer()
# now we are at a current_node where some child is no longer filled
if current_node.left == None:
current_node.left = Node(value)
current_node.left.parent = current_node
elif current_node.right == None:
current_node.right = Node(value)
current_node.right.parent = current_node
class Node:
def __init__(self, value):
self.data = value
self.left = None
self.right = None
self.parent = None
def children_are_filled(self):
left_is_filled = (self.left != None)
right_is_filled = (self.right != None)
return left_is_filled and right_is_filled
def get_next_node_across_layer(self):
if self.parent == None: # node is the root node
return self.left
elif self == self.parent.left: # node is a left node
return self.parent.right
elif self == self.parent.right: # node is a right node
return self.parent.get_next_node_across_layer().left
(Machine Learning; 60 min)
a) Create the following normalization functions that replace the elements of an input list x
with their normalized values.
minmax_normalize
- linearly "squashes" the range of the data into the interval $[0,1].$
>>> minmax_normalize([6, 7, 8, 9])
[0.0, 0.333333, 0.666667, 1.0]
>>> minmax_normalize([4, 13, 3, 5, 5])
[0.1, 1.0, 0.0, 0.2, 0.2]
percentile_normalize
- the percentile of an element is the portion of the data that is less than the element.
>>> percentile_normalize([6, 7, 8, 9])
[0.0, 0.25, 0.5, 0.75]
>>> percentile_normalize([4, 13, 3, 5, 5])
[0.2, 0.8, 0.0, 0.4, 0.4]
zscore_normalize
- computes the number of standard deviations that an element is above (or below) the mean. For example, in the dataset [1, 2, 3, 4, 5, 6, 7]
the standard deviation is 2
, so we have
>>> zscore_normalize([1, 2, 3, 4, 5, 6, 7])
[-1.5, -1, -0.5, 0.0, 0.5, 1, 1.5]
b) Create a the following distance functions that compute the distance between two input lists x
and y
(interpreted as vectors).
euclidean_distance
- the square root of the sum of squared differences of the components: $$\sqrt{ \sum_{i=1}^n (x_i-y_i)^2 }$$
hamming_distance
- the sum of absolute differences of the components: $$\sum_{i=1}^n |x_i-y_i|$$
cosine_distance
- the angle between the vectors (using the dot product): $$\vec{x} \cdot \vec{y} = ||x|| \cdot ||y|| \cdot \cos \theta \qquad \Rightarrow \qquad \theta = \arccos \left( \dfrac{\vec{x} \cdot \vec{y}}{||x|| \cdot ||y||} \right)$$
Come up with tests to demonstrate that your function implements each method correctly.
(Software Engineering; 60 min)
Update your game per the following specifications:
a) Change attack_technology
and defense_technology
to be a player attribute which is applied to all of the player's units during combat. (Previously, it was implemented as a unit attribute.)
attack_technology=1
, then all of its units get an attack boost of +1
during battle.b) When moving your units randomly, make sure that they are given the option to stay put. Also, create a unit attribute speed
that represents the number of spaces that a unit can move per turn. The speed values are listed in the table below.
speed=2
, the unit might stay put, or it might move 1 unit left, or it might move 1 unit up and 1 unit right, or it might move 1 unit down and 1 more unit down, and so on.c) Create a unit attribute armor
that represents the number of hits that the unit can withstand before being destroyed. Whenever a unit is hit during battle, its armor
decreases by 1
. Once armor
reaches 0
, the unit is destroyed. The initial armor values are listed in the table below.
Ship | Armor | Speed |
-------------------------------
Scout | 1 | 2 |
Destroyer | 1 | 1 |
Cruiser | 2 | 1 |
Battlecruiser | 2 | 1 |
Battleship | 3 | 1 |
Dreadnaught | 3 | 1 |
(30 min)
a) Write a function count_compression(string)
that takes a string and compresses it into a list of tuples, where each tuple indicates the count of times a particular symbol was repeated.
>>> count_compression('aaabbcaaaa')
[('a',3), ('b',2), ('c',1), ('a',4)]
>>> count_compression('22344444')
[(2,2), (3,1), (4,5)]
b) Write a function count_decompression(compressed_string)
that decompresses a compressed string to return the original result.
>>> count_decompression([('a',3), ('b',2), ('c',1), ('a',4)])
'aaabbcaaaa'
>>> count_decompression([(2,2), (3,1), (4,5)])
'22344444'
(45 min)
Write a class BinaryTree
which is similar to the doubly linked list, but with the following exceptions:
head
node is now called the root
nodeleft
and right
instead of just a single attribute next
The only method you need to implement (as of now) is append
.
>>> tree = BinaryTree(0)
>>> tree.append(1)
>>> tree.append(2)
>>> tree.append(3)
Note: at this point, the tree's internal state should look as follows
0
/ \
1 2
/
3
>>> tree.append(4)
>>> tree.append(5)
>>> tree.append(6)
Note: at this point, the tree's internal state should look as follows
0
/ \
1 2
/| |\
3 4 5 6
(60 min)
Update your game system so that on each turn, each player's construction points (CP) are increased by 10, and they purchase "attack technology" or "defense technology" for a ship (chosen at random) whenever possible.
"Attack technology" and "defense technology" are upgrades to a unit's baseline attack and defense stats during battle. For example, if a unit's baseline attack is $2,$ and it has an attack technology of $3,$ then in battle it is treated as having an attack level of $2+3=5.$
The costs for technology upgrades are as follows:
Attack Technology | CP Cost |
-----------------------------
0 | n/a | (all ships start with attack technology 0)
1 | 20 |
2 | 30 |
3 | 40 |
Defense Technology | CP Cost |
------------------------------
0 | n/a | (all ships start with defense technology 0)
1 | 20 |
2 | 30 |
3 | 40 |
Note: the CP Cost tells how many more CPs are needed to get to the next attack technology. So, if a unit was at attack technology 0, then to upgrade it to attack technology 2, you would have pay 50 CPs.
(60 min)
a) Write a recursive function gradient_descent(f,x0,alpha=0.01,delta=0.001,precision=0.0001)
that uses gradient descent to estimate the minimum of $f(x),$ given the initial guess $x=x_0.$ Here's a visualization of how it works.
The gradient descent algorithm is
$$x_{n+1} = x_n - \alpha f'(x_n),$$where $\alpha$ (alpha) is a constant called the learning rate.
Note that you will have to write a helper function to estimate $f'(x_n)$ using a difference quotient,
$$f'(x_n) \approx \dfrac{f(x_n+\delta) - f(x_n-\delta)}{2\delta},$$where $\delta$ (delta) is chosen as a very small constant. (For our cases, $\delta = 0.001$ should be sufficient.)
The function should recurse until successive guesses are within precision
amount of each other.
b) Test gradient_descent
on a dummy example: estimate the minimum value of
using the initial guess $x_0 = 0.$ (Note: do not work out the derivative by hand! You should estimate it numerically.)
c) Use gradient_descent
to estimate the minimum value of
using the initial guess $x_0 = 0.$ (Note: do not work out the derivative by hand! You should estimate it numerically.)
(30 min)
Observe that we can time how long it takes to run some code by using the time
module:
from time import time
start = time()
# begin code to time
myList = [k**2+k+1 for k in range(10000)]
mySum = sum(myList)
# end code to time
diff = time()-start
print(diff)
a) Create a function measure_time(f,x)
that measures the amount of time it takes to apply the function f
to a single input x
.
Note: you may get a slightly different time than shown in the example below, because the time that the computer takes to run the function is not always exactly the same.
def f(x):
my_list = [k**2+k+1 for k in range(x)]
my_sum = sum(my_list)
return my_sum
measure_time(f,10000)
---
0.003872394561767578
b) Create a function time_statistics(f,x,num_samples=100)
that computes num_samples
samples of the time it takes to apply the function f
to the input x
, and returns the resulting mean and standard deviation of the time.
>>> time_statistics(f,10000)
{
'mean': 0.003220198154449463,
'stdev': 0.00035378651456685384
}
(60 min)
a) Create a function gap_swap_sort(x)
that sorts a list x
in a way that is similar to swap_sort
, except instead of looping through the whole list each time, we loop through "gaps" of the list and cut the gap in half after each iteration.
For example, for the following list of length $8,$ the first gap is ${8/2} = 4.$
$$ [3, 2, 5, 1, 7, 4, 1, 2] \\ [\underline{\mathbf{3}}, 2, 5, 1, \underline{\mathbf{7}}, 4, 1, 2]\\ [3, \underline{\mathbf{2}}, 5, 1, 7, \underline{\mathbf{4}}, 1, 2]\\ [3, 2, \underline{\mathbf{1}}, 1, 7, 4, \underline{\mathbf{5}}, 2]\\ [3, 2, 1, \underline{\mathbf{1}}, 7, 4, 5, \underline{\mathbf{2}}] $$Now, the gap becomes $4/2 = 2.$
$$ [3, 2, 1, 1, 7, 4, 5, 2] \\ [\underline{\mathbf{1}}, 2, \underline{\mathbf{3}}, 1, 7, 4, 5, 2] \\ [1, \underline{\mathbf{1}}, 3, \underline{\mathbf{2}}, 7, 4, 5, 2] \\ [1, 1, \underline{\mathbf{3}}, 2, \underline{\mathbf{7}}, 4, 5, 2] \\ [1, 1, 3, \underline{\mathbf{2}}, 7, \underline{\mathbf{4}}, 5, 2] \\ [1, 1, 3, 2, \underline{\mathbf{5}}, 4, \underline{\mathbf{7}}, 2] \\ [1, 1, 3, 2, 5, \underline{\mathbf{2}}, 7, \underline{\mathbf{4}}] $$Now, the gap becomes $2/2 = 1,$ which is just an iteration of swap_sort
:
We can't make the gap any smaller, so we continue looping through with gap size $1$ (i.e. swap_sort
) until the list is sorted.
b) State the time complexity of gap_swap_sort
using big-O notation. Provide some justification for your answer.
Your answer here:
import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator
def labeled_scatter_plot(data, gridsize=[5,5], fontsize=12):
fig, ax = plt.subplots()
ax.xaxis.set_minor_locator(MultipleLocator(0.5))
ax.yaxis.set_minor_locator(MultipleLocator(0.5))
for item in data:
x = item['x']
y = item['y']
color = item['color']
label = item['label']
ax.text(x, y, label, fontsize=fontsize, color=color, horizontalalignment='center', verticalalignment='center')
x_max, y_max = gridsize
plt.xlim(-0.5 ,x_max-0.5)
plt.ylim(-0.5, y_max-0.5)
plt.grid(which='minor')
plt.show()
data1 = [
{'x': 3, 'y': 2, 'color': 'red', 'label': 'S1'},
{'x': 2, 'y': 1, 'color': 'blue', 'label': 'D2'}
]
data2 = [
{'x': 3, 'y': 1, 'color': 'red', 'label': 'S1'},
{'x': 3, 'y': 1, 'color': 'blue', 'label': 'D2'}
]
labeled_scatter_plot(data1)
labeled_scatter_plot(data2)
a) Refactor your code per the following specifications:
You need separate classes: Game
, Player
, Unit
, and a class for each type of ship.
Your code needs to be readable -- variables need to be named well, and there shouldn't be too much happening on any single line.
Each method within a class should be something that the class actually does. For example, a Unit does not build a fleet. A player builds a fleet.
Each function should be concise and should do just one thing. (Extreme case: if a function can't be seen on a single screen without scrolling, then that's an issue.)
b) Use the with the function labeled_scatter_plot
to display the board after each turn. Don't get rid of your text logging. At this point the board display should be an addition, not a replacement for your logging.
Use the naming convention <letter><number>
where <letter>
represents the type of ship and <number>
represents the unit number. So, S1
would correspond to a Scout
that is a player's Unit 1
.
Battlecruiser
should have the letter Bc
and Battleship
should have the label Bs
.Player 1 should be blue, and Player 2 should be red.
c) Modify the function labeled_scatter_plot
so that when two units occupy the same grid square, they do not overlap visually. You should move the units slightly apart while ensuring that they still lie within the required grid square.
c) Show that inverse_by_minors
and inverse
(which leverages rref
) give the same result when applied to several different matrices.
d) State the time complexities of inverse_by_minors
and inverse
using big-O notation. Provide some justification for your answer.
inverse_by_minors
: your answer here
inverse
: your answer here
(30 min)
a) Take your class LinkedList
from before and extend it to become a doubly linked list.
This means that each Node
in the list should have an additional attribute, prev
, which returns the previous node. (It is the opposite of the next
attribute.)
b) Create and demonstrate tests to provide evidence that each node's prev
attribute is set correctly after modifying the list by pushing, inserting, or deleting elements.
(60 min)
a) Write a function tally_sort(x)
that sorts the list x
from least to greatest using the following process:
Greate an array of indices consisting of the numbers from the minimum to the maximum element.
Go through the list x
and tally up the count in each bin.
Transform the tallies into the desired sorted list.
For example, if x = [1, 4, 1, 2, 7, 5, 2]
, then the histogram would be:
Tally: | 2 | 2 | 0 | 1 | 1 | 0 | 1 |
Index: | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
And therefore the sorted list would be
[1, 1, 2, 2, 4, 5, 7]
State the time complexity of tally_sort
using big-O notation. Provide some justification for your answer.
Your answer here:
b) Write a function card_sort(x)
that sorts the list x
from least to greatest by using the method that a person would use to sort cards by hand.
For example, to sort x = [12, 11, 13, 5, 6]
, we would go through the list and repeatedly put the next number we encounter in the appropriate place relative to the numbers that we have already gone through.
So, the sequence would be:
[12, 11, 13, 5, 6]
[11, 12, 13, 5, 6]
[11, 12, 13, 5, 6]
[5, 11, 12, 13, 6]
[5, 6, 11, 12, 13]
State the time complexity of card_sort
using big-O notation. Provide some justification for your answer.
Your answer here:
(90 min)
a) Refactor your game code to the following specifications:
Use (at least) the following classes: Game
, Player
, Unit
. (Do NOT use Ship
in place of Unit
; e.g. later on there will be a type of unit Colony
which is not a ship.)
An object should NOT mutate the underlying structure of a different object. Each object should mutate its own underlying structure.
Player
, you should NOT be explicitly increasing or decreasing the coordinates of units. Rather, you should say something like unit.move()
, and then the unit will adjust its own coordinates as appropriate.Clean up any for
loops so that you are looping over the elements of the list, rather than the index of the list (unless you actually need the index).
for i in range(len(units)): self.units[i].move()
for unit in self.units: unit.move()
b) Introduce the following additional functionality:
Each player starts out with $50$ construction points (CPs) and then randomly builds a fleet until no more units can be used.
The cost of building a particular type of unit is shown in the table below. Each player should build a random fleet using the 50 construction points.
So that might be a Dreadnaught (24 cp), a Destroyer (9 cp), and a Battlecruiser (15cp) for a total cost of 48 cp.
Or it might be 3 Scouts (3 x 6cp = 18cp), a Battleship (20cp), and a Cruiser (12cp) for a total of 50cp.
Basically just build a fleet by randomly choosing units until there's not enough construction points to purchase any more units. (To be clear, your code should perform the random generation of the fleet -- i.e. you shouldn't just manually pick it randomly.)
Combat resolution proceeds as follows:
Units attack each other in the order of their attack class (A
first, then B
, and so on). If two ships have the same attack class, then you can fall back to random choice.
Let hit_threshold
equal the attacker's attack strength subtracted by the defender's defense strength. Then, roll a $6$-sided die.
If the die roll is less than or equal to hit_threshold
, then a hit is scored.
Also, regardless of hit_threshold
, a roll of 1
always scores a hit.
If a unit is hit, then it is immediately destroyed.
Name | CP Cost | Attack Class | Attack Strength | Defense Strength |
-----------------------------------------------------------------------------
Scout | 6 | E | 3 | 0 |
Destroyer | 9 | D | 4 | 0 |
Cruiser | 12 | C | 4 | 1 |
Battlecruiser | 15 | B | 5 | 1 |
Battleship | 20 | A | 5 | 2 |
Dreadnaught | 24 | A | 6 | 3 |
(30 min)
a) Extend your Matrix
class to include a method recursive_determinant()
that computes the determinant recursively using the cofactor method.
b) Show that recursive_determinant
and determinant
(which leverages rref
) give the same result when applied to several different matrices.
c) State the time complexities of recursive_determinant
and determinant
using big-O notation. Provide some justification for your answer.
recursive_determinant
: your answer here
determinant
: your answer here
(30 min)
a) Write a function simple_sort(x)
that takes an input list x
and sorts its elements from least to greatest by repeatedly finding the smallest element and moving it to a new list. Don't use Python's built-in min
function.
State the time complexity of simple_sort
using big-O notation.
Your answer:
b) Write a recursive function swap_sort(x)
that sorts the list from least to greatest by repeatedly going through each pair of adjacent elements and swapping them if they are in the wrong order.
State the time complexity of swap_sort
using big-O notation.
Your answer:
(45 min)
Extend your class LinkedList
to include the following methods:
push(new_data)
- insert a new node at the head of the linked list, containing the new_data
insert_after(prev_node, new_data)
- insert a new node after the given prev_node
, containing the new_data
append(new_data)
- append a new node to the end of the linked list, containing the new_data
delete(data)
- delete the first node in the linked list whose data is the given data
search(data)
- return True
if the given data
is in the linked list, and False
otherwise
Be sure to test your code AND RUN IT THROUGH THE LINTER.
(90 min)
Implement the following game:
There are 2 players with 3 units each, on a $5 \times 5$ grid ($0 \leq x, y \leq 4$).
During each turn, each unit moves randomly to an adjacent space. (Diagonal spaces are not adjacent)
random
module and use random.randint(a,b)
to return a random integer N
such that a <= N <= b
After moving, if two units occupy the same space, one unit (chosen randomly) is destroyed.
random.randint(a,b)
.The game stops when either all of a player's units have been destroyed, or after $100$ turns, whichever happens soonest.
The purpose of this problem is to give you more experience using classes. Don't just write a bunch of solo functions. You need to organize your code well. We'll be adding a bunch of additional features onto this game in the future.
RUN YOUR CODE THROUGH THE LINTER!
>>> game = Game()
>>> game.state()
Player 1:
Unit 1: (0,2)
Unit 2: (0,2)
Unit 3: (0,2)
Player 2:
Unit 1: (4,2)
Unit 2: (4,2)
Unit 3: (4,2)
>>> game.complete_turn()
Player 1:
Unit 1: (0,2) -> (0,1)
Unit 2: (0,2) -> (0,3)
Unit 3: (0,2) -> (1,2)
>>> game.resolve_combat()
>>> game.complete_turn()
Player 2:
Unit 1: (4,2) -> (4,1)
Unit 2: (4,2) -> (3,2)
Unit 3: (4,2) -> (4,3)
>>> game.resolve_combat()
>>> game.complete_turn()
Player 1:
Unit 1: (0,1) -> (1,1)
Unit 2: (0,3) -> (0,4)
Unit 3: (1,2) -> (2,2)
>>> game.resolve_combat()
>>> game.complete_turn()
Player 2:
Unit 1: (4,1) -> (3,1)
Unit 2: (3,2) -> (2,2)
Unit 3: (4,3) -> (3,3)
>>> game.resolve_combat()
Combat at (2,2)
Player 1 (Unit 3) vs Player 2 (Unit 2)
Survivor: Player 2 (Unit 2)
>>> game.state()
Player 1:
Unit 1: (1,1)
Unit 2: (0,4)
Player 2:
Unit 1: (3,1)
Unit 2: (2,2)
Unit 3: (3,3)
>>> game.run_to_completion()
>>> game.state()
Player 1:
Unit 1: (4,0)
Player 2:
Unit 2: (3,1)
Unit 3: (1,1)
>>> game.num_turns
100
>>> game.winner
2
(30 min)
Extend your Matrix
class to include a method determinant()
that computes the determinant.
You should do this by using the same code as in your rref()
method, but keeping track of the scaling factors by which you divide the rows of the matrix. The determinant is just the product of these scaling factors (assuming the determinant is nonzero).
rref
a bit more complex), you can modify your rref()
method to include an optional parameter return_determinant
which is by default set to False
. Then, your determinant()
method can consist of just return self.rref(return_determinant=True)
.Be sure to address the following edge cases:
Test your determinant
method on 4 different examples. You can use this matrix determinant calculator to check the results.
RUN YOUR CODE THROUGH THE LINTER!
(30 min)
a) Write a function median(x)
that computes the median of an input list. Make your own tests.
b) Write a function cov(x,y)
that computes the covariance of two lists x
an y
.
Also write a function corr(x,y)
that computes the Pearson correlation coefficient between the two lists.
Make your own tests.
(30 min)
Figure out what what following function does, and modify it so that it is readable. Be sure to rename the function and variables with more informative names, and expand out the nested list comprehension. Remember to test that your implementation gives the same result as the original function.
def doesSomething(z):
return [['abcdefghijklmnopqrstuvwxyz'.index(x) for x in y] for y in z.split(' ')]
(30 min)
The Collatz function is defined as
$$f(n) = \begin{cases} n \, / \, 2 & \text{if } n \text{ is even} \\ 3n+1 & \text{if } n \text{ is odd} \end{cases}$$The Collatz conjecture is that by repeatedly applying this function to any positive number, the result will eventually reach the cycle
$$1 \to 4 \to 2 \to 1.$$For example, repeatedly applying the Collatz function to the number $13,$ we have:
$$13 \to 40 \to 20 \to 10 \to 5 \to 16 \to 8 \to 4 \to 2 \to 1$$a) Without using recursion, create a function collatz_iterations(number)
that computes the number of iterations of the Collatz function that it takes for the input number to reach $1.$
>>> collatz_iterations(13)
9
b) Using recursion, create a function collatz_iterations(number, iterations=0)
that computes the number of iterations of the Collatz function that it takes for the input number to reach $1.$
>>> collatz_iterations(13)
9
(45 min)
Create a class LinkedList
and a class Node
which together implement a singly linked list.
The class LinkedList
should have exactly one attribute:
head
: gives the node at the beginning of the linked listEach node should have exactly two attributes:
data
: returns the contents of the nodenext
: returns the next nodeLinkedList
should have exactly three methods:
print()
: prints the contents of the nodes, starting at the headlength()
: returns the number of nodes in the linked listappend()
: appends a new node to the tail of the linked listFor this assignment, you only need to implement the functionality above. We will continue this problem on the next assignment (in which you will implement insert
, delete
, and search
). But you don't have to do that yet.
Make sure not to use any python lists in your answer.
>>> x = LinkedList(4)
>>> x.head.data
4
>>> x.append(8)
>>> x.head.next.data
8
>>> x.append(9)
>>> x.print()
4
8
9
>>> x.length()
3
(45 min)
a) Extend your Matrix
class to include a method inverse()
that computes the inverse matrix using Gaussian elimination (i.e. your rref
method).
If the matrix is not invertible, print a message that explains why -- is it be cause it's singular, or because it's non-square?
Test your inverse
method on 2 different examples. Verify that when you multiply the original matrix by its inverse, you get the identity matrix.
b) Extend your Matrix
class to include a method solve(b)
that uses reduced row echelon form to solve the equation $Ax=b,$ where $A$ is the matrix itself and $b$ is a column vector.
Use an augmented matrix, and let your rref()
method do the brunt of the work.
For example, the system $$ \begin{cases} x_1 + x_2 = 6 \\ x_1 - x_2 = 2 \end{cases} $$
can be represented as the equation $Ax=b$ where
$$ A = \begin{pmatrix} 1 & 1 \\ 1 & -1 \end{pmatrix}, \quad b = \begin{pmatrix} 6 \\ 2 \end{pmatrix} $$The solution to the system is $x_1 = 4$ and $x_2 = 2,$ so the solution to $Ax=b$ is $x = \begin{pmatrix} 4 \\ 2 \end{pmatrix}.$
>>> A = Matrix(elements = [[1, 1],
[1, -1])
>>> b = [6, 2]
>>> A.solve(b)
[4, 2]
(30 min)
a) Create a function find_all_numbers_divisible_by_a_and_b(n,a,b)
that lists the numbers $1$ through $n$ that are divisible by both $a$ and $b$. Your function should consist of simply returning a list comprehension.
>>> find_all_numbers_divisible_by_a_and_b(100,6,15)
[30, 60, 90]
b) Rewrite the following function to consist of simply returning a list comprehension.
def compute_all_products(M,N):
product = []
for m in range(M):
for n in range(N):
product = m * n
result.append(product)
return product
(60 min)
a) WITHOUT using recursion, create a function skip_factorial(n)
that computes the product
>>> skip_factorial(6)
48
>>> skip_factorial(7)
105
Now, rewrite the function skip_factorial(n)
WITH recursion.
b) WITHOUT using recursion, create a function unlist(x)
that removes outer parentheses from a list until either a) the final list consists of multiple elements, or b) no more lists exist.
>>> unlist([[[[1], [2,3], 4]]])
[[1], [2,3], 4]
>>> unlist([[[[1]]]])
1
Now, rewrite the function unlist(x)
WITH recursion.
(45 min)
Write several classes to implement the following hierarchy. Be sure to use inheritance when appropriate.
School has a name
School has many courses
School has many teachers
School has many students
School has a principal
Principal has a first name
Principal has a last name
Course has a teacher
Teacher has a first name
Teacher has a last name
Course has many students
Student has a first name
Student has a last name
Student has a grade
When writing your hierarchy, use the given information:
The principal of AI High is Geoffrey Hinton.
The courses, teachers, and students are as follows:
The students have the following grade levels:
(30 min)
Extend the rref
method in your Matrix
class to work on matrices that do not reduce to the identity matrix.
>>> A = Matrix(elements = [[0, 1, 2],
[3, 6, 9],
[2, 6, 8]])
>>> A.rref().show()
[1, 0, 0]
[0, 1, 0]
[0, 0, 0]
Create the following additional examples to test your code:
A) 2 examples of square matrices that reduce to different rref forms
B) 2 examples of tall rectangular matrices (i.e. more rows than columns) that reduce to different rref forms
C) 2 examples of wide rectangular matrices (i.e. more columns than rows) that reduce to different rref forms
Estimated time: (20 min)
a) Write a function mean
that computes the mean of a list of numbers.
The mean is defined as follows:
$$\text{Mean}(x_1, x_2, \ldots, x_n) = \dfrac{x_1 + x_2 + \ldots + x_n}{n}$$Test your code on the following example:
>>> mean([1,3,4,9,6,5])
4.666666666666667
b) Write a function var
that computes the variance of a list of numbers.
Let $\bar{x}$ denote $\text{Mean}(x_1, x_2, \ldots, x_n).$ The variance is defined as follows:
$$\text{Var}(x_1, x_2, \ldots, x_n) = \dfrac{(x_1-\bar{x})^2 + (x_2-\bar{x})^2 + \ldots + (x_n-\bar{x})^2}{n}$$Test your code on the following example:
>>> var([1,3,4,9,6,5])
6.222222222222222
c) Write a function stdev
that computes the standard deviation of a list of numbers.
Again, let $\bar{x}$ denote $\text{Mean}(x_1, x_2, \ldots, x_n).$ The standard deviation is defined as follows:
$$\text{StDev}(x_1, x_2, \ldots, x_n) = \sqrt{ \dfrac{(x_1-\bar{x})^2 + (x_2-\bar{x})^2 + \ldots + (x_n-\bar{x})^2}{n} }$$Test your code on the following example:
>>> var([1,3,4,9,6,5])
2.494438257849294
d) In English words, what do the mean, variance, and standard deviation represent?
(5 min)
Consider the code below:
class Animal(object):
pass
class Dog(Animal):
def __init__(self, name):
self.name = name
class Cat(Animal):
def __init__(self, name):
self.name = name
class Person(object):
def __init__(self, name, pets):
self.name = name
self.pets = pets
Fill in the following blanks with "is a", "has a", or "has many" as appropriate:
(30 min)
Notice that we can approximate a zero of a function by repeatedly computing the zero of the tangent line:
a) Create a function zero_of_tangent_line(c)
that computes the zero of the tangent line to the function $f(x)=x^3+x-1$ at the point $x=c.$
Test your code on the following example:
>>> zero_of_tangent_line(0.5)
0.714286
b) Create a function estimate_solution(precision)
that estimates the solution to $f(x) = x^3+x-1$ by repeatedly calling zero_of_tangent_line
until the next guess is within precision
of the previous guess.
Test your code on the following example:
>>> estimate_solution(0.01)
0.6823284
(45 min)
Extend your Matrix
class to include a method rref
that converts the matrix to reduced row echelon form. You should use the row reduction algorithm shown below:
For this problem, you may assume that the matrix is a square matrix that reduces to the identity matrix. We will deal with other edge-cases (like zero rows and rectangular matrices) in a future assignment.
>>> A = Matrix(elements = [[0, 1, 2],
[3, 6, 9],
[2, 6, 8]])
>>> A.rref().show()
[1, 0, 0]
[0, 1, 0]
[0, 0, 1]
Also, test your code on 2 more examples of your own choosing.
%matplotlib inline
import matplotlib.pyplot as plt
plt.style.use('bmh')
plt.plot(
[0, 1, 2, 0], # X-values
[0, 1, 0, 0], # Y-values
color='blue'
)
plt.gca().set_aspect("equal")
a) Write a class Rectangle
.
include the attributes base
, height
, color
, perimeter
, area
, and vertices
.
base
, height
, and color
should be used as parametersinclude a method describe()
that prints out the attributes of the rectangle.
include a method render()
that renders the rectangle on a cartesian plane. (You can use plt.plot()
and plt.gca()
and plot.gca().set_aspect("equal")
as shown above.)
>>> rect = Rectangle(5,2,'red')
>>> rect.describe()
Base: 5
Height: 2
Color: red
Perimeter: 14
Area: 10
Vertices: [(0,0), (5,0), (5,2), (0,2)]
>>> rect.render()
b) Write a class RightTriangle
.
Include the attributes base
, height
, color
, perimeter
, area
, and vertices
.
Include a method describe()
that prints out the attributes of the right triangle.
include a method render()
that draws the triangle on a cartesian plane.
>>> tri = RightTriangle(5,2,'blue')
>>> tri.describe()
Base: 5
Height: 2
Color: blue
Perimeter: 12.3851648071
Area: 5
Vertices: [(0,0), (5,0), (0,2)]
>>> tri.render()
c) Write a class Square
that inherits from Rectangle
. Here's an example of how to implement inheritance.
Note: You should not be manually writing any methods in the Square
class. The whole point of using inheritance is so that you don't have to duplicate code.
>>> sq = Square(5,'green')
>>> sq.describe()
Base: 5
Height: 5
Color: green
Perimeter: 20
Area: 25
Vertices: [(0,0), (5,0), (5,5), (0,5)]
>>> sq.render()
d) Write a class Shape
with
attributes base
, height
, and color
methods describe()
and render()
Then, rewrite your classes Rectangle
and RightTriangle
so that they are child classes that inherit from the parent class Shape
.
The reason why we might do this is that we'd like to avoid duplicating the describe()
and render()
methods in each subclass. This way, you'll only have to write the these methods once, in the Shape
class.
The polynomial $f(x)=x^3+x-1$ has a root on the interval $[0,1].$
a) Create a function update_bounds(bounds)
that guesses a root halfway between the bounds, determines whether the guess was too high or too low, and updates the bounds
accordingly.
For example, starting with the bounds $[0,1],$ the guess would be $0.5.$ This guess is too low because $f(0.5) = -0.375 < 0.$ So, the updated bounds would be $[0.5, 1].$
Now, using the bounds $[0.5,1]$, the next guess would be $0.75.$ This guess is too high because $f(0.75) = 0.171875 > 0.$ So, the updated bounds would be $[0.5, 0.75].$
Example:
>>> update_bounds([0,1])
[0.5, 1]
>>> update_bounds([0.5,1])
[0.5, 0.75]
b) Create a function estimate_solution(precision)
that estimates the solution to $f(x) = x^3+x-1$ by repeatedly calling update_bounds
until the estimated solution is guaranteed to be within precision
of the actual solution. You can start with the bounds $[0,1]$ again.
The actual solution is $0.68233 \ldots$, but this number should not appear anywhere in your code. Instead, you can find the maximum error
of an estimated solution by taking half the length between the bounds.
estimate_solution(0.1)
but not estimate_solution(0.01).
Implement the following helper methods in your matrix class.
get_pivot_row(self, column_index)
: returns the index of the topmost row that has a nonzero entry in the desired column_index
and such that all entries left of column_index
are zero. Otherwise, if no row exists, return None
.
swap_rows(self, row_index1, row_index2)
: swap the row at row_index1
with the row at row_index2
.
scale_row(self, row_index)
: divide the entire row at row_index
by the row's first nonzero entry.
clear_below(self, row_index)
:
row_index
.row_index
from the rows below, so that for any row below row_index
, the entry at column $j$ is zero.clear_above(self, row_index)
:
row_index
.row_index
from the rows above, so that for any row above row_index
, the entry at column $j$ is zero.Watch out!
Remember that the first row/column of a matrix has the index 0
, not 1
.
If row1
is "below" row2
in a matrix, then row1
actually has a higher index than row2
. This is because the 0
index corresponds to the very top row.
Example:
>>> A = Matrix(elements = [[0, 1, 2],
[3, 6, 9],
[2, 6, 8]])
>>> A.get_pivot_row(0) (TEST ID: 1)
1
>>> A.swap_rows(0,1)
>>> A.show() (TEST ID: 2)
[3, 6, 9]
[0, 1, 2]
[2, 6, 8]
>>> A.scale_row(0)
>>> A.show() (TEST ID: 3)
[1, 2, 3]
[0, 1, 2]
[2, 6, 8]
>>> A.clear_below(0)
>>> A.show() (TEST ID: 4)
[1, 2, 3]
[0, 1, 2]
[0, 2, 2]
>>> A.get_pivot_row(1) (TEST ID: 5)
1
>>> A.scale_row(1)
>>> A.show() (TEST ID: 6)
[1, 2, 3]
[0, 1, 2]
[0, 2, 2]
>>> A.clear_below(1)
>>> A.show() (TEST ID: 7)
[1, 2, 3]
[0, 1, 2]
[0, 0, -2]
>>> A.get_pivot_row(2) (TEST ID: 8)
2
>>> A.scale_row(2)
>>> A.show() (TEST ID: 9)
[1, 2, 3]
[0, 1, 2]
[0, 0, 1]
>>> A.clear_above(2)
>>> A.show() (TEST ID: 10)
[1, 2, 0]
[0, 1, 0]
[0, 0, 1]
>>> A.clear_above(1)
>>> A.show() (TEST ID: 11)
[1, 0, 0]
[0, 1, 0]
[0, 0, 1]
a) Implement a function labelEvenOdd
that takes a list of numbers and labels each number as even or odd. Return a list comprehension so that the function takes up only two lines, as follows:
def labelEvenOdd(numbers):
return [<your code here>]
Example:
>>> labelEvenOdd([1,2,3,5,8,11])
[(1,'odd'),(2,'even'),(3,'odd'),(5,'odd'),(8,'even'),(11,'odd')]
b) Rewrite the function below so that it does not use any dictionary comprehensions. Test your function on some input to make sure it gives the same output as the function below. (Note that you may have to scroll sideways to see the full function.)
def doSomething(sentence):
return {word: sentence.replace(word, 'barnacle') for word in sentence.split(' ')}
Watch this video on Big-O notation. Then, provide the Big-O notation for the following computations:
Consider the sequence defined recursively as
$a_n = a_{n-1} - 2 a_{n-2}, a_0 = 0, a_1 = 1.$
a) Write a function firstNTerms
that returns a list of the first $n$ terms of the sequence: $[a_0, a_1, a_2, \ldots, a_{n}]$
>>> firstNTerms(20)
[0, 1, 1, -1, -3, -1, 5, 7, -3, -17, -11, 23, 45, -1, -91, -89, 93, 271, 85, -457]
b) Watch this video on recursion. Then, write a function nthTerm
that computes the $n$th term of the sequence, using recursion.
>>> nthTerm(30)
-24475
a) Refactor your Matrix
class so that the code is super clean.
Be sure to name your variables well. For example, you shouldn't have an attribute A.matrix
-- rather, it should be A.elements
.
b) In your matrix class, rewrite __init__
to take only the following parameters, all of which are optional:
shape
: a tuple containing the number of rows and columns of the matrix. By default, set shape
equal to None
.
fill
: if a number, then set all the matrix elements equal to the value of fill
. However, if fill
is set to diag
, then set the diagonal elements equal to 1
and all other elements equal to 0
. By default, fill
equal to 0
.
elements
: set the matrix elements equal to the corresponding values of the given array. By default, set elements
equal to None.
Also, include the following attributes:
shape
: returns the shape of the matrix as a tuple
elements
: returns the matrix elements as an array
Example:
>>> A = Matrix(shape=(2,3))
>>> A.show()
[0, 0, 0]
[0, 0, 0]
>>> A.shape (TEST ID: A1)
(2,3)
>>> A.elements (TEST ID: A2)
[[0,0,0],[0,0,0]]
>>> B = Matrix(shape=(2,3),fill=1)
>>> B.show() (TEST ID: A3)
[1, 1, 1]
[1, 1, 1]
>>> C = Matrix(shape=(2,3),fill='diag')
>>> C.show() (TEST ID: A4)
[1, 0, 0]
[0, 1, 0]
>>> D = Matrix(elements=[[1,2,3],
[4,5,6]])
>>> D.show() (TEST ID: A5)
[1, 2, 3]
[4, 5, 6]
c) Extend your Matrix
class to overload equality and indexing.
Equality:
implement a method isEqual
that checks if all the elements in the matrix are equal to their counterparts in another matrix.
overload the equality operator (==
) using __eq__
.
Indexing:
implement a method get
that gets the matrix element at the desired index.
implement a method set
that sets the matrix element at the desired index equal to some desired value.
overload the indexing operator ([]
) using __getitem__
and __setitem__
.
:
as indicated by the example. To check if the parameter param
is equal to :
, you can use isinstance(param, slice)
Example:
>>> A = Matrix(elements = [[1, 2],
[3, 4]])
>>> A[0,1] (TEST ID: B1)
2
>>> A[0,1] = 5
>>> A[0,1] (TEST ID: B2)
5
>>> A.show()
[1, 5]
[3, 4]
>>> B = Matrix(elements = [[1, 5]
[3, 4]])
>>> A == B (TEST ID: B3)
True
>>> B[1,:] (TEST ID: B4)
[3, 4]
>>> B[:,0] (TEST ID: B5)
[1, 3]
>>> B[1,:] = [8, 9] (TEST ID: B6)
>>> B.show()
[1, 5]
[8, 9]
>>> A == B (TEST ID: B7)
False
a) Implement a stack. That is, create a class Stack
which operates on a list using the following methods:
push
: add a new item on top of the stack
pop
: remove the top item from the stack
peek
: return the top item without modifying the stack
Examples:
>>> s = Stack()
>>> s.push('a')
>>> s.push('b')
>>> s.push('c')
>>> s.show()`
['a', 'b', 'c']
>>> s.pop()
>>> s.show()
['a', 'b']
>>> s.peek()
'b'
>>> s.show()
['a', 'b']
b) Implement a queue. That is, create a class Queue
which operates on a list using the methods enqueue
and dequeue
, and peek
.
enqueue
: add a new item to the back of the queue
dequeue
: remove the item at the front of the queue
peek
: return the item at the front without modifying the queue
Example:
>>> q = Queue()
>>> q.enqueue('a')
>>> q.enqueue('b')
>>> q.enqueue('c')
>>> q.show()
['c', 'b', 'a']
>>> q.dequeue()
>>> q.show()
['c', 'b']
>>> q.peek()
['b']
>>> q.show()
['c', 'b']
a) Write a function makeNested
which takes a "flat" dictionary and converts it into a nested dictionary based on the key names.
Example:
>>> colors = {
'animal_bumblebee': ['yellow', 'black'],
'animal_elephant': ['gray'],
'animal_fox': ['orange', 'white'],
'food_apple': ['red', 'green', 'yellow'],
'food_cheese': ['white', 'orange']
}
>>> makeNested(colors)
{
'animal': {
'bumblebee': ['yellow', 'black'],
'elephant': ['gray'],
'fox': ['orange', 'white']
},
'food': {
'apple': ['red', 'green', 'yellow'],
'cheese': ['white', 'orange']
}
}
b) Write a function flatten
which takes a nested dictionary and converts it into a flat dictionary based on the key names.
Example:
>>> colors = {
'animal': {
'bumblebee': ['yellow', 'black'],
'elephant': ['gray'],
'fox': ['orange', 'white']
},
'food': {
'apple': ['red', 'green', 'yellow'],
'cheese': ['white', 'orange']
}
}
>>> flatten(colors)
{
'animal_bumblebee': ['yellow', 'black'],
'animal_elephant': ['gray'],
'animal_fox': ['orange', 'white'],
'food_apple': ['red', 'green', 'yellow'],
'food_cheese': ['white', 'orange']
}
In Assignment 2, you wrote a matrix class. Clean up your code for your matrix class and generalize it to $m \times n$ matrices.
Additionally,
implement the following attribute: shape
implement the following methods: max
, min
, transpose
, exponent
.
overload the following operators:
+
(__add__
) for matrix addition,-
(__sub__
) for matrix subtraction,*
(__mul__
) for scalar multiplication,@
(__matmul__
) for matrix multiplicationExamples:
>>> A = Matrix([[1, 1, 0],
[2, -1, 0],
[0, 0, 3]])
>>> A.shape()
(3, 3)
>>> A.max()
3
>>> A.min()
-1
>>> A.transpose().show()
[[1, 2, 0],
[1, -1, 0],
[0, 0, 3]]
>>> A.exponent(3).show()
[[3, 3, 0],
[6, -3, 0],
[0, 0, 27]]
In Assignment 1, we encountered the trivial encoding function which maps
' '
$\rightarrow 0,$
'a'
$\rightarrow 1,$
'b'
$\rightarrow 2,$
and so on.
Using a linear encoding function $s(x) = 2x+3,$ the message 'a cat'
can be encoded as follows:
Original message: 'a cat'
Trivial encoding: [1, 0, 3, 1, 20]
Linear encoding: [5, 3, 9, 5, 43]
a) Create a function linearEncoding(string,a,b)
which encodes a string using the scrambling function $s(x) = ax+b,$
Example:
>>> linearEncoding('a cat', 2, 3)
[5, 3, 9, 5, 43]
b) Decode the message
[377,
717,
71,
513,
105,
921,
581,
547,
547,
105,
377,
717,
241,
71,
105,
547,
71,
377,
547,
717,
751,
683,
785,
513,
241,
547,
751],
given that it was encoded with a linear encoding function $s(x) = ax+b$ where $a,b \in \{ 0, 1, 2, \ldots, 100 \}.$
Hint: try running through all combinations of $a$ and $b,$ checking if they correspond to some decoded message, and if so, then printing out that decoded message. Then, you can visually inspect the results to find the one that makes sense.
a) Write a function intersection
that computes the intersection of two tuples.
Example:
intersection( (1,2,'a','b'), (2,3,'a') )
$\rightarrow$ (2,'a')
b) Write a function union
that computes the union of two tuples.
Example:
union( (1,2,'a','b'), (2,3,'a') )
$\rightarrow$ (1,2,3,'a','b')
Write a function countCharacters
that counts the number of each character in a string and returns the counts in a dictionary. Lowercase and uppercase letters should not be treated differently.
Example:
countCharacters('A cat!!!')
$\rightarrow$ {'a': 2, 'c': 1, 't': 1, ' ': 1, '!': 3}
Create a Matrix
class with the methods show
, add
, subtract
, scale
, and multiply
for $2 \times 2$ matrices.
The input to a Matrix
object is a multi-dimensional array (i.e. an array of arrays).
Examples:
A = Matrix([[1,3],[2,4]])
A.show()
[[ 1, 3 ],
[ 2, 4 ]]
B = Matrix([[1,0],[2,-1]])
C = A.add(B)
C.show()
[[ 2, 3 ],
[ 4, 3 ]]
D = A.subtract(B)
D.show()
[[ 0, 3 ],
[ 0, 5 ]]
E = A.scale(2)
E.show()
[[ 2, 6 ],
[ 4, 8 ]]
F = A.multiply(B)
E.show()
[[ 7, -3 ],
[ 10, -4 ]]
a) Write a function letters2numbers
that converts a string to a list of numbers, where space = 0, a = 1, b = 2, and so on.
Example:
letters2numbers('a cat')
$\rightarrow$ [1,0,3,1,20]
b) Write a function numbers2letters
that converts a list of numbers to the corresponding string.
Example:
numbers2letters([1,0,3,1,20])
$\rightarrow$ 'a cat'
Write a function isSymmetric
that checks if a string reads the same forwards and backwards.
Examples:
isSymmetric('racecar')
$\rightarrow$ True
isSymmetric('batman')
$\rightarrow$ False
a) Write a function convertToBase10
that converts a number from base-2 to base-10.
Example:
convertToBase10(10011)
$\rightarrow$ 19
because $1 \cdot 2^{4} + 0 \cdot 2^3 + 0 \cdot 2^2 + 1 \cdot 2^1 + 1 \cdot 2^0 = 19$b) Write a function convertToBase2
that converts a number from base-10 to base-2.
Example:
convertToBase2(19)
$\rightarrow$ 10011
Write a function isPrime
that checks if a number is prime.
Examples:
isPrime(59)
$\rightarrow$ True
isPrime(51)
$\rightarrow$ False
Hint: Check for divisibility within a for loop.