Lecture Code
1 | # Slicing |
1 | # Aggregation |
1 List Comprehensions
A list comprehension is a compact way to create a list whose elements are the results of applying a fixed expression to elements in another sequence.[<map exp> for <name> in <iter exp> if <filter exp>]
It might be helpful to note that we can rewrite a list comprehension as an equivalent for statement. See the example.
1 | new_lst = [] |
Let’s break down an example:[x * x - 3 for x in [1, 2, 3, 4, 5] if x % 2 == 1]
In this list comprehension, we are creating a new list after performing a series of operations to our initial sequence [1, 2, 3, 4, 5]
. We only keep the elements that satisfy the filter expression x % 2 == 1
(1, 3, and 5). For each retained element, we apply the map expression x*x - 3
before adding it to the new list that we are creating, resulting in the output [-2, 6, 22]
.
Note: The if
clause in a list comprehension is optional.
Questions
What would Python display?
1 | >>> [i + 1 for i in [1, 2, 3, 4, 5] if i % 2 == 0] |
2 Trees
In computer science, trees are recursive data structures that are widely used in various settings. The diagram to the right is an example of a tree.
Notice that the tree branches downward. In computer science, the root of a tree starts at the top, and the leaves are at the bottom.
Some terminology regarding trees:
• Parent node: A node that has branches. Parent nodes can have
multiple branches.
• Child node: A node that has a parent. A child node can only belong
to one parent.
• Root: The top node of the tree. In our example, the node that contains 7 is the root.
• Label: The value at a node. In our example, all of the integers are
values.
• Leaf: A node that has no branches. In our example, the nodes that
contain −4, 0, 6, 17, and 20 are leaves.
• Branch: A subtree of the root. Note that trees have branches, which
are trees themselves: this is why trees are recursive data structures.
• Depth: How far away a node is from the root. In other words, the
number of edges between the root of the tree to the node. In the
diagram, the node containing 19 has depth 1; the node containing 3
has depth 2. Since there are no edges between the root of the tree and
itself, the depth of the root is 0.
• Height: The depth of the lowest leaf. In the diagram, the nodes
containing −4, 0, 6, and 17 are all the “lowest leaves,” and they have
depth 4. Thus, the entire tree has height 4.
In computer science, there are many different types of trees. Some vary in the number of branches each node has; others vary in the structure of the tree.
mplementation
1 | # Constructor |
A tree has both a value for the root node and a sequence of branches, which are also trees. In our implementation, we represent the branches as a list of trees. Since a tree is an abstract data type, our choice to use lists is just an implementation detail.
• The arguments to the constructor tree are the value for the root node and a list of branches.
• The selectors for these are label and branches.
Note that branches returns a list of trees and not a tree directly. It’s important to distinguish between working with a tree and working with a list of trees.
We have also provided a convenience function, is_leaf.
Let’s try to create the tree below.
1 | # Example tree construction |
Questions
2.1 Write a function that returns the largest number in a tree.
When we talk about recursion, what we should think. That’s a problem, especially dealing with trees. The base case is also a leaf, then we use recursion to find the largest label including current label. Then we have the following.
1 | def tree_max(t): |
2.2 Write a function that returns the height of a tree. Recall that the height of a tree is the length of the longest path from the root to a leaf.
1 | def height(t): |
2.3 Write a function that takes in a tree and squares every value. It should return a new tree. You can assume that every item is a number.
1 | def square_tree(t): |
2.4 Write a function that takes in a tree and a value x
and returns a list containing the nodes along the path required to get from the root of the tree to a node containing x
.
If x
is not present in the tree, return None
. Assume that the entries of the tree are unique.
For the following tree, find path(t, 5)
should return [2, 7, 6, 5]
This seems to be a hard problem, the way we think of it is first the base case. So what is the base case of this question? I think maybe when the label of current tree is exactly the same as x, then we can return a list which consists x or label(tree). Then we should think the recursive case. When we are in the location of some path, we need to keep a paths variable, which is a list, consisting of a series of paths. But in this problem, we only have one path. So if x is in the tree, it must be one path, otherwise the path may be None. Then when we find a path, we need to append the path to our current label. And the game is over. We can either explictly return None or do nothing, which implictly returns None
1 | def find_path(tree, x): |
2.5 Write a function that takes in a tree and a depth k
and returns a new tree that contains only the first k
levels of the original tree.
For example, if t
is the tree shown in the previous question, then prune(t, 2)
should return the following tree.
1 | def prune(t, k): |
2.6 We can represent the hailstone sequence as a tree in the figure below, showing the route different numbers take to reach 1. Remember that a hailstone sequence starts with a number n, continuing to n/2 if n is even or 3n + 1 if n is odd, ending with 1. Write a function hailstone tree(n, h) which generates a tree of height h, containing hailstone numbers that will reach n.
Hint: A node of a hailstone tree will always have at least one, and at most two branches (which are also hailstone trees). Under what conditions do you add the second branch?
1 | def hailstone_tree(n, h): |