[2021-Fall] Hw04 of CS61A of UCB


Mobiles


Q2: Weights

Implement the planet data abstraction by completing the planet constructor and the size 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 size.

從問題的描述中我們可以知道什么是 planet. 就是一個長度為 2 的 list, 內容是 ['planet', size]. 可以參考 mobile 函數, 這兩個函數的代碼是很類似的

def planet(size):
    """Construct a planet of some size."""
    assert size > 0
    return ['planet', size]


def size(w):
    """Select the size of a planet."""
    assert is_planet(w), 'must call size on a planet'
    return w[1]

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:

  1. 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 a mobile hanging at the end of the left arm of weight 10, the torque on the left side of our mobile is 50.
  2. Each of the mobiles hanging at the end of its arms is balanced.

Planets themselves are balanced, as there is nothing hanging off of them.

這個問題需要用遞歸的方法來解決. 我們判斷一個 mobile 平衡的條件如下

  1. 它是 planet, 根據描述可以知道本身是平衡的
  2. 它是 arm, 並且 total_weight(left_arm) == total_weight(right_arm). 並且它的所有子樹都要符合這個平衡的條件. 注意不要漏掉對子樹的判斷

寫出代碼的關鍵: 要區分開 arm, planet, mobile 這三個概念並且要能夠知道用什么對應的函數來處理. 😢

def balanced(m):
    """Return whether m is balanced.

    >>> t, u, v = examples()
    >>> balanced(t)
    True
    >>> balanced(v)
    True
    >>> w = mobile(arm(3, t), arm(2, u))
    >>> balanced(w)
    False
    >>> balanced(mobile(arm(1, v), arm(1, w)))
    False
    >>> balanced(mobile(arm(1, w), arm(1, v)))
    False
    >>> from construct_check import check
    >>> # checking for abstraction barrier violations by banning indexing
    >>> check(HW_SOURCE_FILE, 'balanced', ['Index'])
    True
    """
    # planet is balanced 
    if is_planet(m):
        return True

    left_arm, right_arm = left(m), right(m)
    
    # end(...arm) will is a mobile or a planet
    left_val = length(left_arm) * total_weight(end(left_arm))
    right_val = length(right_arm) * total_weight(end(right_arm))
    return left_val == right_val and balanced(end(left_arm)) and balanced(end(right_arm))

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_trees for the ends of its arms. As a reminder, the description of the tree data abstraction can be found here.

我們在這個問題中想要把 mobile 轉換為 tree. 遞歸解法的 base case 是當我們遇到葉子結點而且是 planet 的時候. 否則我們就遞歸分析它的子樹

def totals_tree(m):
    """Return a tree representing the mobile with its total weight at the root.

    >>> t, u, v = examples()
    >>> print_tree(totals_tree(t))
    3
      2
      1
    >>> print_tree(totals_tree(u))
    6
      1
      5
        3
        2
    >>> print_tree(totals_tree(v))
    9
      3
        2
        1
      6
        1
        5
          3
          2
    >>> from construct_check import check
    >>> # checking for abstraction barrier violations by banning indexing
    >>> check(HW_SOURCE_FILE, 'totals_tree', ['Index'])
    True
    """
    if is_planet(m):
        return tree(size(m))

    return tree(total_weight(m), [totals_tree(i) for i in [end(left(m)), end(right(m))]])

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.

遞歸解法的 base case 仍然是葉子結點, 我們要檢查它的 label 是否為 'loki'. 如果是的話, 我們就創建一個新的葉子結點並返回. 否則我們返回本來的葉子結點即可.

def replace_loki_at_leaf(t, lokis_replacement):
    """Returns a new tree where every leaf value equal to "loki" has
    been replaced with lokis_replacement.

    >>> yggdrasil = tree('odin',
    ...                  [tree('balder',
    ...                        [tree('loki'),
    ...                         tree('freya')]),
    ...                   tree('frigg',
    ...                        [tree('loki')]),
    ...                   tree('loki',
    ...                        [tree('sif'),
    ...                         tree('loki')]),
    ...                   tree('loki')])
    >>> laerad = copy_tree(yggdrasil) # copy yggdrasil for testing purposes
    >>> print_tree(replace_loki_at_leaf(yggdrasil, 'freya'))
    odin
      balder
        freya
        freya
      frigg
        freya
      loki
        sif
        freya
      freya
    >>> laerad == yggdrasil # Make sure original tree is unmodified
    True
    """
    if is_leaf(t):
        if label(t) == 'loki':
            return tree(lokis_replacement)
        return t
    return tree(label(t), [replace_loki_at_leaf(b, lokis_replacement) for b in branches(t)])

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 labelis exactly one character.

  1. 什么是這道題的 base case ? 我們要記住從根結點出發到當前所在結點一路經過的結點拼起來的字符串是什么. 舉例來說, 在一開始的時候我們在根結點, 我們的字符串是 label(t). 也就是 h. 當我們到了根結點的第一個子結點的時候, 我們就得到了 h + i = hi. 為了實現這樣的功能我們可以另外寫個遞歸函數. 然后在 has_path 里面調用即可. 一個提高效率的方法是如果我們當前得到的字符串不是目標字符串的一部分(開頭位置), 那么我們顯然沒有必要繼續到更深的子樹里面尋找. 這也是遞歸的剪枝操作.
  2. 怎么把問題分解為更簡單的子問題 ? 我們可以使用 python 提供的 any 函數, 只要任意一個子樹上存在這樣的路徑即可.
def has_path(t, word):
    """Return whether there is a path in a tree where the entries along the path
    spell out a particular word.

    >>> greetings = tree('h', [tree('i'),
    ...                        tree('e', [tree('l', [tree('l', [tree('o')])]),
    ...                                   tree('y')])])
    >>> print_tree(greetings)
    h
      i
      e
        l
          l
            o
        y
    >>> has_path(greetings, 'h')
    True
    >>> has_path(greetings, 'i')
    False
    >>> has_path(greetings, 'hi')
    True
    >>> has_path(greetings, 'hello')
    True
    >>> has_path(greetings, 'hey')
    True
    >>> has_path(greetings, 'bye')
    False
    >>> has_path(greetings, 'hint')
    False
    """
    assert len(word) > 0, 'no path for empty word.'
    def helper(t, cur):
        if cur == word:
            return True
        if cur not in word:
            return False
        return any([helper(b, cur + label(b)) for b in branches(t)])
    return helper(t, label(t))

Trees


Q7: Preorder

Define the function preorder, which takes in a tree as an argument and returns a list of all the entries in the tree in the order that print_tree would print them.

The following diagram shows the order that the nodes would get printed, with the arrows representing function calls.

先序遍歷是遍歷樹的一個基本方法. 我們要先記住根結點的值再去訪問它的子樹上的結點.

def preorder(t):
    """Return a list of the entries in this tree in the order that they
    would be visited by a preorder traversal (see problem description).

    >>> numbers = tree(1, [tree(2), tree(3, [tree(4), tree(5)]), tree(6, [tree(7)])])
    >>> preorder(numbers)
    [1, 2, 3, 4, 5, 6, 7]
    >>> preorder(tree(2, [tree(4, [tree(6)])]))
    [2, 4, 6]
    """
    result = []
    def helper(t):
        if t is not None:
            result.append(label(t))
            for b in branches(t):
                helper(b)
    helper(t)
    return result

Data Abstraction


Q8: Interval 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:

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.

這道題的計算系統其實是用來計算電阻的. 我們知道電阻會存在誤差(比如 \(\pm5\%\)), 所以真正的電阻值應該是在一個區間里面的. 這就是這個所謂的的 interval 表示的. 所以 interval 其實也就是一個長度為 2 的 list 而已. 要得到它兩側的范圍我們只要根據索引返回 x[0]x[1] 即可.

def interval(a, b):
    """Construct an interval from a to b."""
    assert a <= b, 'Lower bound cannot be greater than upper bound'
    return [a, b]


def lower_bound(x):
    """Return the lower bound of interval x."""
    return x[0]


def upper_bound(x):
    """Return the upper bound of interval x."""
    return x[1]

Q9: 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.

def mul_interval(x, y):
   """Return the interval that contains the product of any value in x and any
   value in y."""
   p1 = x[0] * y[0]
   p2 = x[0] * y[1]
   p3 = x[1] * y[0]
   p4 = x[1] * y[1]
   return [min(p1, p2, p3, p4), max(p1, p2, p3, p4)]

這里面有很多違反了數據抽象原則的操作, 比如我們訪問電阻的上下界的時候不應該直接用 x[0]x[1]. ⚠️ 這其實是破壞了封裝性, 萬一以后我們換個一個實現電阻的方式, 這個代碼就用不了了. 所以正確的方法應該是用前面實現的 lower_bound()upper_bound() 這兩個函數. 除此之外, 我們在創建一個 interval 的時候也應該用 interval 這個構造函數.

def mul_interval(x, y):
    """Return the interval that contains the product of any value in x and any
    value in y."""
    p1 = lower_bound(x) * lower_bound(y)
    p2 = lower_bound(x) * upper_bound(y)
    p3 = upper_bound(x) * lower_bound(y)
    p4 = upper_bound(x) * upper_bound(y)
    return interval(min(p1, p2, p3, p4), max(p1, p2, p3, p4))

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.

這個代碼如果和 mul_interval 的幾乎一模一樣, 只是換了個操作符而已. 如果你跟我一樣用的是 vim 作為編輯器, 那么用 <ctrl-v> 選中四個 * 之后我們用 r- 就可以快速替換掉了.

def sub_interval(x, y):
    """Return the interval that contains the difference between any value in x
    and any value in y."""
    p1 = lower_bound(x) - lower_bound(y)
    p2 = lower_bound(x) - upper_bound(y)
    p3 = upper_bound(x) - lower_bound(y)
    p4 = upper_bound(x) - upper_bound(y)
    return interval(min(p1, p2, p3, p4), max(p1, p2, p3, p4))

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 assertstatement to Alyssa's code to ensure that no such interval is used as a divisor:

檢查是否除數 > 0

def div_interval(x, y):
    """Return the interval that contains the quotient of any value in x divided by
    any value in y. Division is implemented as the multiplication of x by the
    reciprocal of y."""
    assert lower_bound(y) > 0, "AssertionError!"
    reciprocal_y = interval(1 / upper_bound(y), 1 / lower_bound(y))
    return mul_interval(x, reciprocal_y)

Q10: 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 theformula for parallel resistors can be written in two algebraically equivalent ways:

par1(r1, r2) = (r1 * r2) / (r1 + r2)

or

par2(r1, r2) = 1 / (1/r1 + 1/r2)

He has written the following two programs, each of which computes the parallel_resistors formula differently:

def par2(r1, r2):
    one = interval(1, 1)
    rep_r1 = div_interval(one, r1)
    rep_r2 = div_interval(one, r2)
    return div_interval(one, add_interval(rep_r1, rep_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.

如果你想知道這個的詳細解釋的話, 可以看一下這個 鏈接

def check_par():
    """Return two intervals that give different results for parallel resistors.

    >>> r1, r2 = check_par()
    >>> x = par1(r1, r2)
    >>> y = par2(r1, r2)
    >>> lower_bound(x) != lower_bound(y) or upper_bound(x) != upper_bound(y)
    True
    """
    r1 = interval(5, 7)  
    r2 = interval(5, 7)  
    return r1, r2


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM