
Homework 4: Sequences, Trees hw04.zip
Required questions
Arms-length recursion
Before we get started, a quick comment on recursion with tree data structures. Consider the following function.
1 | def min_depth(t): |
The line flagged with !!!
is an “arms-length” recursion violation. Although our code works correctly when it is present, by performing this check we are doing work that should be done by the next level of recursion—we already have an if-statement that handles any inputs to min_depth
that are leaves, so we should not include this line to eliminate redundancy in our code.
1 | def min_depth(t): |
Arms-length recursion is not only redundant but often complicates our code and obscures the functionality of recursive functions, making writing recursive functions much more difficult. We always want our recursive case to be handling one and only one recursive level. We may or may not be checking your code periodically for things like this.
Mobiles
Acknowledgements This mobile example is based on a classic problem from MIT Structure and Interpretation of Computer Programs, Section 2.2.2.
We are making a planetarium mobile. A mobile is a type of hanging sculpture. A binary mobile consists of two arms. Each arm is a rod of a certain length, from which hangs either a planet or another mobile. For example, the below diagram shows the left and right arms of Mobile A, and what hangs at the ends of each of those arms.
We will represent a binary mobile using the data abstractions below.
- A
mobile
must have both a leftarm
and a rightarm
. - An
arm
has a positive length and must have something hanging at the end, either amobile
orplanet
. - A
planet
has a positive mass, and nothing hanging from it.
Q2: Weights
Below are the implementations of the various data abstractions used in mobiles. The mobile
and arm
data abstractions have been completed for you.
Your job is to implement the planet
data abstraction by completing the planet
constructor and the mass
selector so that a planet is represented using a two-element list where the first element is the string 'planet'
and the second element is its mass.
Implementation of the Mobile Data Abstraction (for your reference, no need to do anything here):
1 | def mobile(left, right): |
Implementation of the Arm Data Abstraction (for your reference, no need to do anything here):
1 | def arm(length, mobile_or_planet): |
(Your turn!) Fill out the implementation of the Planet Data Abstraction!
1 | def planet(mass): |
Total Weight implementation (for your reference and understanding, no need to do anything here):
The total_weight
example is provided to demonstrate use of the mobile, arm, and planet abstractions for your reference. You do not need to implement anything here. You may use the total_weight
function in questions 3 and 4.
The examples
function builds and returns three mobiles. These mobiles are used in the doctest examples for total_weight
, a function that takes in either a mobile or a planet and returns its total weight. The total weight of a planet is just its mass. The total weight of a mobile is the sum of the masses of all the planets hanging from the mobile.
1 | def examples(): |
Use Ok to test your code:
1 | python3 ok -q total_weight✂️ |
Q3: Balanced
Implement the balanced
function, which returns whether m
is a balanced mobile. A mobile is balanced if both of the following conditions are met:
- The torque applied by its left arm is equal to that applied by its right arm. The torque of the left arm is the length of the left rod multiplied by the total weight hanging from that rod. Likewise for the right. For example, if the left arm has a length of
5
, and there is amobile
hanging at the end of the left arm of total weight10
, the torque on the left side of our mobile is50
. - Each of the mobiles hanging at the end of its arms is itself balanced.
Planets themselves are balanced, as there is nothing hanging off of them.
Reminder: You may use the
total_weight
function defined for you in Question 2 (as well as any of the other functions defined in Question 2).
1 | def balanced(m): |
Use Ok to test your code:
1 | python3 ok -q balanced✂️ |
Q4: Totals
Implement totals_tree
, which takes in a mobile
or planet
and returns a tree
whose root is the total weight of the input. For a planet
, totals_tree
should return a leaf. For a mobile
, totals_tree
should return a tree whose label is that mobile
‘s total weight, and whose branches are totals_tree
s for the ends of its arms. As a reminder, the description of the tree data abstraction can be found here.
1 | def totals_tree(m): |
Use Ok to test your code:
1 | python3 ok -q totals_tree✂️ |
More Trees
Q5: Replace Loki at Leaf
Define replace_loki_at_leaf
, which takes a tree t
and a value lokis_replacement
. replace_loki_at_leaf
returns a new tree that’s the same as t
except that every leaf label equal to "loki"
has been replaced with lokis_replacement
.
If you want to learn about the Norse mythology referenced in this problem, you can read about it here.
1 | def replace_loki_at_leaf(t, lokis_replacement): |
Use Ok to test your code:
1 | python3 ok -q replace_loki_at_leaf✂️ |
Q6: Has Path
Write a function has_path
that takes in a tree t
and a string word
. It returns True
if there is a path that starts from the root where the entries along the path spell out the word
, and False
otherwise. (This data structure is called a trie, and it has a lot of cool applications, such as autocomplete). You may assume that every node’s label
is exactly one character.
1 | def has_path(t, word): |
Use Ok to test your code:
1 | python3 ok -q has_path✂️ |
Optional Questions
Data Abstraction
Feel free to reference Section 2.2 for more information on data abstraction.
Acknowledgements. This interval arithmetic example is based on a classic problem from Structure and Interpretation of Computer Programs, Section 2.1.4.
Introduction. Alyssa P. Hacker is designing a system to help people solve engineering problems. One feature she wants to provide in her system is the ability to manipulate inexact quantities (such as measurements from physical devices) with known precision, so that when computations are done with such approximate quantities the results will be numbers of known precision. For example, if a measured quantity x lies between two numbers a and b, Alyssa would like her system to use this range in computations involving x.
Alyssa’s idea is to implement interval arithmetic as a set of arithmetic operations for combining “intervals” (objects that represent the range of possible values of an inexact quantity). The result of adding, subracting, multiplying, or dividing two intervals is also an interval, one that represents the range of the result.
Alyssa suggests the existence of an abstraction called an “interval” that has two endpoints: a lower bound and an upper bound. She also presumes that, given the endpoints of an interval, she can create the interval using data abstraction. Using this constructor and the appropriate selectors, she defines the following operations:
1 | def str_interval(x): |
Q7: Interval Abstraction
Alyssa’s program is incomplete because she has not specified the implementation of the interval abstraction. She has implemented the constructor for you; fill in the implementation of the selectors.
1 | def interval(a, b): |
Use Ok to unlock and test your code:
1 | python3 ok -q interval -u |
Q8: Interval Arithmetic
After implementing the abstraction, Alyssa decided to implement a few interval arithmetic functions.
This is her current implementation for interval multiplication. Unfortunately there are some data abstraction violations, so your task is to fix this code before someone sets it on fire.
1 | def mul_interval(x, y): |
Use Ok to unlock and test your code:
1 | python3 ok -q mul_interval -u |
Interval Subtraction
Using a similar approach as mul_interval
and add_interval
, define a subtraction function for intervals. If you find yourself repeating code, see if you can reuse functions that have already been implemented.
1 | def sub_interval(x, y): |
Use Ok to unlock and test your code:
1 | python3 ok -q sub_interval -u |
Interval Division
Alyssa implements division below by multiplying by the reciprocal of y
. A systems programmer looks over Alyssa’s shoulder and comments that it is not clear what it means to divide by an interval that spans zero. Add an assert
statement to Alyssa’s code to ensure that no such interval is used as a divisor:
1 | def div_interval(x, y): |
Use Ok to unlock and test your code:
1 | python3 ok -q div_interval -u |
Q9: Par Diff
After considerable work, Alyssa P. Hacker delivers her finished system. Several years later, after she has forgotten all about it, she gets a frenzied call from an irate user, Lem E. Tweakit. It seems that Lem has noticed that the formula for parallel resistors can be written in two algebraically equivalent ways:
1 | par1(r1, r2) = (r1 * r2) / (r1 + r2) |
or
1 | par2(r1, r2) = 1 / (1/r1 + 1/r2) |
He has written the following two programs, each of which computes the parallel_resistors
formula differently:
1 | def par1(r1, r2): |
Lem points out that Alyssa’s program gives different answers for the two ways of computing. Find two intervals r1
and r2
that demonstrate the difference in behavior between par1
and par2
when passed into each of the two functions.
Demonstrate that Lem is right. Investigate the behavior of the system on a variety of arithmetic expressions. Make some intervals r1
and r2
, and show that par1
and par2
can give different results.
1 | def check_par(): |