Lab06 题解 (UCB CS61A@2021 Fall)


Write a function which takes in a list lst, an argument entry, and another argument elem. This function will check through each item in lst to see if it is equal to entry. Upon finding an item equal to entry, the function should modify the list by placing elem into lst right after the item. At the end of the function, the modified list should be returned.

See the doctests for examples on how this function is utilized.

Important: Use list mutation to modify the original list. No new lists should be created or returned.

Note: If the values passed into entry and elem are equivalent, make sure you’re not creating an infinitely long list while iterating through it. If you find that your code is taking more than a few seconds to run, the function may be in a loop of inserting new values.

注意我们不能 for 循环来一边遍历这个 list 一边进行修改, 我记得我之前在《Effective Python》看过这一点. 其实自己 debug 就可以发现, 因为一开始用 for i in range(len(lst)) 的时候就固定了, 但其实你在 for 循环里面会插入新的值, 这个 list 其实是变得更长的(但是 i 还是在原来的范围里), 所以后面超过本来长度的元素就会看不到.

注意下面这个代码是错误的🙅‍♂️

python

def insert_items(lst, entry, elem):
    is_the_same = (entry == elem)
    while True:
        no_entry = True
        for i in range(len(lst)):
            if lst[i] == entry:
                if i == len(lst) - 1:
                    lst.append(elem)
                else:
                    lst.insert(i + 1, elem)
                no_entry = False
            # avoid infinite loop
            if is_the_same:                 
                i += 1
        if no_entry:
            return lst

正确的解法应该是用 while 循环搭配 list.index(x[, start[, end]]) 方法, 代码如下:

python

def insert_items(lst, entry, elem):
    """Inserts elem into lst after each occurence of entry and then returns lst.

    >>> test_lst = [1, 5, 8, 5, 2, 3]
    >>> new_lst = insert_items(test_lst, 5, 7)
    >>> new_lst
    [1, 5, 7, 8, 5, 7, 2, 3]
    >>> double_lst = [1, 2, 1, 2, 3, 3]
    >>> double_lst = insert_items(double_lst, 3, 4)
    >>> double_lst
    [1, 2, 1, 2, 3, 4, 3, 4]
    >>> large_lst = [1, 4, 8]
    >>> large_lst2 = insert_items(large_lst, 4, 4)
    >>> large_lst2
    [1, 4, 4, 8]
    >>> large_lst3 = insert_items(large_lst2, 4, 6)
    >>> large_lst3
    [1, 4, 6, 4, 6, 8]
    >>> large_lst3 is large_lst
    True
    >>> # Ban creating new lists
    >>> from construct_check import check
    >>> check(HW_SOURCE_FILE, 'insert_items',
    ...       ['List', 'ListComp', 'Slice'])
    True
    """
    pos, cnt = 0, 0
    for i in lst:
        if i == entry:
            cnt += 1
    
    while cnt > 0:
        idx = lst.index(entry, pos)
        pos = idx + 1
        if idx == len(lst) - 1:
            lst.append(elem)
        else:
            lst.insert(idx + 1, elem)
        cnt -= 1
    return lst

Implement count_occurrences, which takes in an iterator t and returns the number of times the value x appears in the first n elements of t. A value appears in a sequence of elements if it is equal to an entry in the sequence.

Note: You can assume that t will have at least n elements.

这一道题主要是要让我们学会使用 iternext 这两个函数. 我们可以用 while n > 0 来控制只访问前 n 个位置, 做一个简单的判读来计数即可

python

def count_occurrences(t, n, x):
    """Return the number of times that x appears in the first n elements of iterator t.

    >>> s = iter([10, 9, 10, 9, 9, 10, 8, 8, 8, 7])
    >>> count_occurrences(s, 10, 9)
    3
    >>> s2 = iter([10, 9, 10, 9, 9, 10, 8, 8, 8, 7])
    >>> count_occurrences(s2, 3, 10)
    2
    >>> s = iter([3, 2, 2, 2, 1, 2, 1, 4, 4, 5, 5, 5])
    >>> count_occurrences(s, 1, 3)
    1
    >>> count_occurrences(s, 4, 2)
    3
    >>> next(s)
    2
    >>> s2 = iter([4, 1, 6, 6, 7, 7, 8, 8, 2, 2, 2, 5])
    >>> count_occurrences(s2, 6, 6)
    2
    """
    it = iter(t)
    cnt = 0
    while n > 0:
        val = next(it)
        if val == x:
            cnt += 1
        n -= 1
    return cnt

Implement repeated, which takes in an iterator t and returns the first value in t that appears k times in a row.

Note: You can assume that the iterator t will have a value that appears at least k times in a row. If you are receiving a StopIteration, your repeated function is likely not identifying the correct value.

Your implementation should iterate through the items in a way such that if the same iterator is passed into repeated twice, it should continue in the second call at the point it left off in the first. An example of this behavior is in the doctests.

我们在这个问题重要解决两个问题:

  1. 如何找到连续的 k 个值 ? 需要设置 last_val 来记住上一个值是什么, 这样才能和当前的值进行对比. 用一个 while True: 来不断找即可(题目保证一定找得到, 所以不用怕死循环)
  2. 如何保证下一次调用的时候要从上一次离开的位置开始 ? 如果认真看过课程的人可能记得要怎么做, 我们应该用高阶函数, 在 repeated 里面再定义一个函数, 将变量绑定到这个嵌套的函数上. 这样我们就可以保证每次都可以从上一次的位置开始. 忘了的可以看看这个链接 的 2.4.4 Local state

python

def repeated(t, k):
    """Return the first value in iterator T that appears K times in a row.
    Iterate through the items such that if the same iterator is passed into
    the function twice, it continues in the second call at the point it left
    off in the first.

    >>> s = iter([10, 9, 10, 9, 9, 10, 8, 8, 8, 7])
    >>> repeated(s, 2)
    9
    >>> s2 = iter([10, 9, 10, 9, 9, 10, 8, 8, 8, 7])
    >>> repeated(s2, 3)
    8
    >>> s = iter([3, 2, 2, 2, 1, 2, 1, 4, 4, 5, 5, 5])
    >>> repeated(s, 3)
    2
    >>> repeated(s, 3)
    5
    >>> s2 = iter([4, 1, 6, 6, 7, 7, 8, 8, 2, 2, 2, 5])
    >>> repeated(s2, 3)
    2
    """
    assert k > 1
    last_val, it = None, iter(t)
    def helper(k):
        nonlocal last_val
        nonlocal it
        cnt = 0

        while True:
            val = next(it)

            if last_val is None or val != last_val:
                last_val, cnt = val, 1
            else:
                cnt += 1
                if cnt == k:
                    return val
    return helper(k)