[2021-Fall] Hw05 of CS61A of UCB


Q1: Generate Permutations

Given a sequence of unique elements, a permutation of the sequence is a list containing the elements of the sequence in some arbitrary order. For example, [2, 1, 3], [1, 3, 2], and [3, 2, 1] are some of the permutations of the sequence [1, 2, 3].

Implement gen_perms, a generator function that takes in a sequence seq and returns a generator that yields all permutations of seq. For this question, assume that seq will not be empty.

Permutations may be yielded in any order. Note that the doctests test whether you are yielding all possible permutations, but not in any particular order. The built-in sorted function takes in an iterable object and returns a list containing the elements of the iterable in non-decreasing order.

Hint: If you had the permutations of all the elements in seq not including the first element, how could you use that to generate the permutations of the full seq?

Hint: Remember, it's possible to loop over generator objects because generators are iterators!

題意就是要我們返回一個 generator, 這個 generator 會一個個返回列表的所有排列組合. 這個 Hint 明擺着我們要用遞歸的方法解決這個問題. 很容易想到這一道題的 base case, 如下

def gen_perms(seq):
    if len(seq) <= 1:
        yield seq
    ...

現在的問題就是要如何過渡到子問題, 根據提示我們可以猜: 我們應該遞歸處理除了第一個位置以外的元素, 再根據題目的另一個提示, 我們應該假定我們的 gen_perms(seq) 就是返回 seq 的全排列, 那我們只要將我們的第一個元素插入到全排列的任意一個位置即可. 舉個例子來說, 比如我們處理 [1, 2, 3] 的全排列, 假設我們現在已經得到了 [2, 3] 的全排列——[[2, 3], [3, 2]], 那么我們只要將 1 插入到 [2, 3] 的不同位置, 我們就可以得到 [1, 2, 3], [2, 1, 3], [2, 3, 1], 然后我們對 [3, 2] 進行同樣的步驟. 這樣我們就得到了所有的全排列, 然后我們可以寫出如下的代碼

def gen_perms(seq):
    """Generates all permutations of the given sequence. Each permutation is a
    list of the elements in SEQ in a different order. The permutations may be
    yielded in any order.

    >>> perms = gen_perms([100])
    >>> type(perms)
    <class 'generator'>
    >>> next(perms)
    [100]
    >>> try: #this piece of code prints "No more permutations!" if calling next would cause an error
    ...     next(perms)
    ... except StopIteration:
    ...     print('No more permutations!')
    No more permutations!
    >>> sorted(gen_perms([1, 2, 3])) # Returns a sorted list containing elements of the generator
    [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
    >>> sorted(gen_perms((10, 20, 30)))
    [[10, 20, 30], [10, 30, 20], [20, 10, 30], [20, 30, 10], [30, 10, 20], [30, 20, 10]]
    >>> sorted(gen_perms("ab"))
    [['a', 'b'], ['b', 'a']]
    """
    # This problem requires list type, see example above
    if type(seq) != list:
        seq = list(seq)
    # base case
    if len(seq) <= 1:
        yield seq
    else:
        # iterate every permutation in the generator
        for perm in gen_perms(seq[1:]):
            # enumerate every position for insertation
            for i in range(len(seq)):
                yield perm[:i] + seq[:1] + perm[i:]

Q2: Yield Paths

Define a generator function path_yielder which takes in a tree t, a value value, and returns a generator object which yields each path from the root of t to a node that has label value.

Each path should be represented as a list of the labels along that path in the tree. You may yield the paths in any order.

We have provided a skeleton for you. You do not need to use this skeleton, but if your implementation diverges significantly from it, you might want to think about how you can get it to fit the skeleton.

題目讓我們返回從根結點出發到達所有標簽為 value 的路徑. 注意給我們的提示, 我們要實現的函數應該如下所示:

def path_yielder(t, value):
    "*** YOUR CODE HERE ***"
    for _______________ in _________________:
        for _______________ in _________________:
            "*** YOUR CODE HERE ***"

如果你仔細觀察, 或許可以發現這一題的代碼跟 Q1 竟是如此相似, 現在我們開始思考如何來解決這個問題. 首先要處理什么是 base case, 顯然當我們遇到 label 是 value 的結點時候就到達了 base case 可以返回. 而將這個問題分解為更簡單的子問題的方式也很簡單, 我們要從當前結點出發, 遍歷它的每一個子樹, 只要路徑上有就返回, 注意這里我們返回的是子路徑(不包括當前結點), 然后我們加上當前結點的 label 即可.

用一個例子來說, 看下面的 docstring 里面的例子, 假設我們現在要找到所有到達 label 為 3 的所有路徑, 我們從根結點 1 出發, 檢查的它的每一個子樹, 分別是 25. 然后我們接着對 2 這么處理, 在 2 的第一個子樹中我們找到了 3, 此時我們 yield [3]. 當我們回溯到 2 的時候加上 2, 我們又得到了子路徑 [2, 3], 最后我們接着回溯到根結點, 得到 [1] + [2, 3] = [1, 2, 3]

代碼如下:

def path_yielder(t, value):
    """Yields all possible paths from the root of t to a node with the label
    value as a list.

    >>> t1 = tree(1, [tree(2, [tree(3), tree(4, [tree(6)]), tree(5)]), tree(5)])
    >>> print_tree(t1)
    1
      2
        3
        4
          6
        5
      5
    >>> next(path_yielder(t1, 6))
    [1, 2, 4, 6]
    >>> path_to_5 = path_yielder(t1, 5)
    >>> sorted(list(path_to_5))
    [[1, 2, 5], [1, 5]]

    >>> t2 = tree(0, [tree(2, [t1])])
    >>> print_tree(t2)
    0
      2
        1
          2
            3
            4
              6
            5
          5
    >>> path_to_2 = path_yielder(t2, 2)
    >>> sorted(list(path_to_2))
    [[0, 2], [0, 2, 1, 2]]
    """
    if label(t) == value:
        yield [label(t)]
    for b in branches(t):
        for path in path_yielder(b, value):
            yield [label(t)] + path

Q3: 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

Q4: Generate Preorder

Similarly to preorder in Question 3, define the function generate_preorder, which takes in a tree as an argument and now instead yields the entries in the tree in the order that print_tree would print them.

Hint: How can you modify your implementation of preorder to yield from your recursive calls instead of returning them?

仍舊是先序遍歷的問題, 整體解法上很像 Q3, 只是現在我們不需要用一個 result 來存儲遍歷的序列, 因為我們要返回的是 generator. 但這個算法的邏輯還是一致的, 先看根結點, 然后去看它的子樹的結點. 如果想對 yield from 有更深的了解, 我們可以看看這個人的這篇文章

def generate_preorder(t):
    """Yield 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)])])
    >>> gen = generate_preorder(numbers)
    >>> next(gen)
    1
    >>> list(gen)
    [2, 3, 4, 5, 6, 7]
    """
    yield label(t)
    for b in branches(t):
        yield from generate_preorder(b)

Q5: Remainder Generator

Like functions, generators can also be higher-order. For this problem, we will be writing remainders_generator, which yields a series of generator objects.

remainders_generator takes in an integer m, and yields m different generators. The first generator is a generator of multiples of m, i.e. numbers where the remainder is 0. The second is a generator of natural numbers with remainder 1 when divided by m. The last generator yields natural numbers with remainder m - 1 when divided by m.

Hint: To create a generator of infinite natural numbers, you can call the naturals function that's provided in the starter code.

Hint: Consider defining an inner generator function. Each yielded generator varies only in that the elements of each generator have a particular remainder when divided by m. What does that tell you about the argument(s) that the inner function should take in?

這個問題如果你對 python 的 generator 一點都不了解的話, 一時要寫出這樣的代碼是比較困難的, 我會推薦你先閱讀這個教程.

在閱讀上面的材料之后我們開始以前來想這個問題要怎么解決. 其實它就是要返回 m 個 generator, 第 i 個 generator 里面分別是除以 m 得到余數為 i 的所有數.

注意, 我們要的是所有數, 所以我們可以現象我們要怎么得到第一個 generator, 其他 generator 其實都是類似的. 我們只要用一個 while True 死循環來一直找就行, 因為我們用的是 yield, 它每次找到一個就會暫停執行並返回那個數. 這樣我們就得到了這個無限產生我們想要的數的 generator

def helper(i, m):
    num = 1         # loop variable
    while True:
        if num % m == i:
            yield num
        num += 1
# you can give it a test
>>> it = helper(2, 3)
>>> next(it)
2				# 2 % 3 == 2
>>> next(it)
5				# 5 % 3 == 2
>>> next(it)
8				# 8 % 3 == 2

那么我們如何得到一個 generator 的 list 呢, 也很簡單, 我們用 for 循環就好了

def remainders_generator(m):
    """
    Yields m generators. The ith yielded generator yields natural numbers whose
    remainder is i when divided by m.

    >>> import types
    >>> [isinstance(gen, types.GeneratorType) for gen in remainders_generator(5)]
    [True, True, True, True, True]
    >>> remainders_four = remainders_generator(4)
    >>> for i in range(4):
    ...     print("First 3 natural numbers with remainder {0} when divided by 4:".format(i))
    ...     gen = next(remainders_four)
    ...     for _ in range(3):
    ...         print(next(gen))
    First 3 natural numbers with remainder 0 when divided by 4:
    4
    8
    12
    First 3 natural numbers with remainder 1 when divided by 4:
    1
    5
    9
    First 3 natural numbers with remainder 2 when divided by 4:
    2
    6
    10
    First 3 natural numbers with remainder 3 when divided by 4:
    3
    7
    11
    """
    def helper(i, m):
        num = 1         # loop variable
        while True:
            if num % m == i:
                yield num
            num += 1
    for i in range(m):
        yield helper(i, m)


免責聲明!

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



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