[python][算法][CS61a]Lab 5: Python Lists, Trees

提示:本文内容来源于UCB CS61A 2020 Summer课程,详情请点击CS 61A: Structure and Interpretation of Computer Programs


前言

本文为Lab 5: Python Lists, Trees内容


提示:以下是本篇文章正文内容,叙述部分参考题目描述,由本人与chatgpt共同翻译,思路部分及作业部分为作者完成,仅供参考,若有错误欢迎评论指正

Lab 5: Python Lists, Trees

Topics 实验主题

List Comprehensions 列表推导式

列表推导式是一种紧凑而强大的创建新列表的方式,其语法如下:

[<expression> for <element> in <sequence> if <conditional>]

以上推导式可以读作:对于<sequence>中的每一个元素,如果<conditional>的值为真,那么计算其<expression>的值,这些计算结果共同组成新列表

范例如下:

>>> [i**2 for i in [1, 2, 4, 4] if i % 2 == 0]
[4, 16]

其中,对于[1, 2, 3, 4]中的每一个元素i,若满足i % 2 == 0,则我们计算i**2的值,并且将计算结果插入到新列表中

也可以成为,列表推导式将会创建一个新列表,该新列表包含原列表中每个偶数的平方

也可以用python语句来描述上述过程:

>>> lst = []
>>> for i in [1, 2, 3, 4]:
...     if i % 2 == 0:
...         lst = lst + [i**2]
... 
>>> lst
[4, 16]

注意:列表推导式中的if语句是可选的(也可以丢弃)

>>> [i**2 for i in [1, 2, 3, 4]]
[1, 4, 9, 16]

Tree 树

是表示信息层次结构的数据结构

文件系统是一个树结构的样例

如,在cs61a文件夹中,一些文件夹将学生的projectslabhomework区分开

下一层文件夹则区分不同的作业的文件夹,如hw01, lab01, hog等等

在那些文件夹中,又包含了starter filesok

如下提供了一个cs61a的不完整目录

cs61a
homework
lab
projects
hw01
hw02
lab01
lab02
hog
cat
hw01.py
ok
hw01.py
ok
lab01.py
ok
lab02.py
ok
hog.py
ok
cat.py
ok

正如图中所示,与自然界中的树不同,的抽象数据类型时根在顶部,叶在底部

注:本节中的树采用列表之列表(List of lists)的形式表示

一些树的术语:

  • 根(root):树顶部的节点
  • 标签(label):节点的值,可以又label函数(选择器)得到
  • 分支(branches):树的根节点下一层的树,可以又branches函数(选择器)得到
  • 叶子(leaf):没有分支的树
  • 节点(node):树中的任何位置(如根节点、叶节点等)

的抽象数据类型由根节点和一系列的分支组成

创建一颗树,获取根节点值,获取分支,需要使用系列构造器和选择器:

  • 构造器(Constructor)
    • tree(label, branches=[]):使用参数label和列表branches创建tree对象
    • 注意:该构造器的第二个参数branches是可选的,若要创建一颗没有任何分支的树,将该参数留空即可
  • 选择器(Selector)
    • label(tree):返回一颗树根节点的值
    • branches(tree):返回给定的树的所有分支(子树列表)
  • Convenience function
    • is_leaf(tree):若给定的树的分支为空则返回True,否则返回False

例如,树可以由以下代码生成:

number_tree = tree(1,
         [tree(2),
          tree(3,
               [tree(4),
                tree(5)]),
          tree(6,
               [tree(7)])])

这颗树的图形如下:

   1
 / | \
2  3  6
  / \  \
 4   5  7

为从该树种获取数字3(该树第二个分支的根节点的值),可以使用如下方法:

label(branches(number_tree)[1])

函数print_tree以可以由人类阅读的方式打印一颗树,具体形式为:

  • 根节点不缩进
  • 该节点的每个分支均比根节点缩进一级
def print_tree(t, indent=0):
    """Print a representation of this tree in which each node is
    indented by two spaces times its depth from the root.

    >>> print_tree(tree(1))
    1
    >>> print_tree(tree(1, [tree(2)]))
    1
      2
    >>> numbers = tree(1, [tree(2), tree(3, [tree(4), tree(5)]), tree(6, [tree(7)])])
    >>> print_tree(numbers)
    1
      2
      3
        4
        5
      6
        7
    """
    print('  ' * indent + str(label(t)))
    for b in branches(t):
        print_tree(b, indent + 1)

Required Question 必答问题

Lists 列表

Q1: Coordinate 坐标

实现一个名为coords的函数,它接受一个函数fn、一个序列seq和函数fn输出值的下限lower和上限upper作为参数

返回一个坐标对(该坐标对为一个二元列表)的列表,使得:

  • 每个(x, y)对被表示为[x, fn(x)]
  • x坐标是序列中的元素
  • 结果仅包含y坐标在下限和上限之间(含)的坐标对

doctest给出的示例如下

"""
>>> seq = [-4, -2, 0, 1, 3]
>>> fn = lambda x: x**2
>>> coords(fn, seq, 1, 9)
[[-2, 4], [1, 1], [3, 9]]
"""

该问题限制只能一行完成,函数可以用列表推导式实现

def coords(fn, seq, lower, upper):
    "*** YOUR CODE HERE ***"
    return [[x, fn(x)] for x in seq if lower <= fn(x) <= upper]

以上实现可以通过脚本测试

$ python3 ok -q coords --local
=====================================================================
Assignment: Lab 5
OK, version v1.18.1
=====================================================================

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Running tests

---------------------------------------------------------------------
Test summary
    1 test cases passed! No cases failed.
Q2: Riffle Shuffle 洗牌

假设有一副牌(或一串东西),欲要通过洗牌,得到一种新的排列方式:

  • 顶部的牌接着是中间的牌,然后是第二张牌,再是中间之后的牌,以此类推
  • 假设牌(或序列)中有偶数张牌,请编写一个列表推导式,用于生成洗牌后的序列

doctest如下:

"""
>>> riffle([3, 4, 5, 6])
[3, 5, 4, 6]
>>> riffle(range(20))
[0, 10, 1, 11, 2, 12, 3, 13, 4, 14, 5, 15, 6, 16, 7, 17, 8, 18, 9, 19]
"""

对样例的分析如下

0123
原列表3456
洗牌后列表3546
新列表中各数字在原列表中的索引0213
新列表中各数字在新列表中的索引0123
两索引之间的关系00 + 211 + 2
012345678910111213141516171819
原列表012345678910111213141516171819
洗牌后列表010111212313414515616717818919
新列表中各数字在原列表中的索引010111212313414515616717818919
新列表中各数字在新列表中的索引012345678910111213141516171819
两索引之间的关系00 + 1011 + 1022 + 1033 + 1044 + 1055 + 1066 + 1077 + 1088 + 1099 + 10

假设一个数字,在新列表中的索引为i,那么由上述样例可知,该数字在原列表中的索引应为:

  • 先将洗牌后的列表分组,每相邻两个分为一组,该数字的其组号的关系为,即i // 2
  • 在每一组中
    • 第一个数字在原列表中的索引即为其组号i // 2
    • 第二个数字在原列表中的索引为:组号 + 列表长度的一般,即i // 2 + length //2
  • 由于每一组数字中第一个数字为偶数(i % 2 == 0),第二个数字为奇数(i % 2 == 1)
    • 则该数字在新列表中索引与在原列表中索引的关系可以表示为:i // 2 + i % 2 * (length //2)

函数的实现如下:

def riffle(deck):
    """Produces a single, perfect riffle shuffle of DECK, consisting of
    DECK[0], DECK[M], DECK[1], DECK[M+1], ... where M is position of the
    second half of the deck.  Assume that len(DECK) is even.
    """
    "*** YOUR CODE HERE ***"
    return [deck[i//2 + i % 2 * (len(deck)//2)] for i in range(len(deck))]

可以通过脚本测试

$ python3 ok -q riffle --local
=====================================================================
Assignment: Lab 5
OK, version v1.18.1
=====================================================================

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Running tests

---------------------------------------------------------------------
Test summary
    1 test cases passed! No cases failed.

Trees

Q3: Finding Berries

定义一个名为函数berry_finder,该函数接受一棵树作为输入
若树中包含一个值为’berry’的节点则返回True,否则返回False。

提示:使用for循环遍历每一个分支

doctest如下:

"""
>>> scrat = tree('berry')
>>> berry_finder(scrat)
True
>>> sproul = tree('roots', [tree('branch1', [tree('leaf'), tree('berry')]), tree('branch2')])
>>> berry_finder(sproul)
True
>>> numbers = tree(1, [tree(2), tree(3, [tree(4), tree(5)]), tree(6, [tree(7)])])
>>> berry_finder(numbers)
False
>>> t = tree(1, [tree('berry',[tree('not berry')])])
>>> berry_finder(t)
True
"""

本题思路为:

  • 检查根节点值是否为'berry',是则返回True
  • 若不是,则分别在各个分支(子树)中查找'berry'
    • 若找到,则返回True
  • 当所有分支均为找到,则返回False

函数实现如下:

def berry_finder(t):
    """Returns True if t contains a node with the value 'berry' and 
    False otherwise.
    """
    "*** YOUR CODE HERE ***"
    if label(t) == 'berry':
        return True
    for branch in branches(t):
        if berry_finder(branch):
            return True
    return False

以上函数实现可以通过脚本测评

$ python3 ok -q berry_finder --local
=====================================================================
Assignment: Lab 5
OK, version v1.18.1
=====================================================================

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Running tests

---------------------------------------------------------------------
Test summary
    1 test cases passed! No cases failed.
Q4: Sprout leaves

要求实现一个函数sprout_tree

该函数接受一棵树t和一个列表leaves为参数

返回一棵与t相同的新树,但其中每个旧的叶子节点都有一系列新的分支,每个分支对应于leaves中的一个元素

如,首先生成一颗树t = tree(1, [tree(2), tree(3, [tree(4)])])::

  1
 / \
2   3
    |
    4

若调用sprout_leaves(t, [5, 6]),生成的结果如下(*之间的为新添加的叶子):

       1
     /   \
    2     3
   / \    |
 *5* *6*  4
         / \
       *5* *6*

doctest如下:

"""
>>> t1 = tree(1, [tree(2), tree(3)])
>>> print_tree(t1)
1
  2
  3
>>> new1 = sprout_leaves(t1, [4, 5])
>>> print_tree(new1)
1
  2
    4
    5
  3
    4
    5

>>> t2 = tree(1, [tree(2, [tree(3)])])
>>> print_tree(t2)
1
  2
    3
>>> new2 = sprout_leaves(t2, [6, 1, 2])
>>> print_tree(new2)
1
  2
    3
      6
      1
      2
"""

要求不能直接对原始数据类型(即列表)进行操作,限制只能使用给定的构造器和选择器

需要明确以下几点:

  • 假设一棵树的根节点不是叶子节点,那么该树的叶子节点,也是该树的所有子树的所有叶子节点,即若在一棵树的叶子节点上添加分支,则相当于在该树的所有子树上添加分支
  • 函数sprout_tree的功能为返回一颗添加给定叶子节点后的树
  • 树的构造器tree(label, branches=[])中,第一个参数为根节点的值,第二个参数为由其所有子树组成的列表,即branches中的每一个元素都是一颗树

由上可知,我们有如下思路实现函数sprout_tree

  1. 若参数t没有分支,即给定的树只有一个根节点(同时也是叶子节点),则:
    1. 以列表leaves中的每个元素为节点值生成一颗树,每颗树共同组成一个列表branches
    2. 创建一颗树,以原t的值label为根节点的值,以上一步中得到的列表branches为分支创建一颗新的树new_tree
    3. 返回new_tree
  2. 若参数t有分支,则:
    1. 对参数t分支中的每一个子树调用sprout_tree(添加leaves),其返回的树共同构成子树``branches`
    2. 以原t的值label为根节点的值,以上一步中得到的列表branches为分支创建一颗新的树new_tree
    3. 返回new_tree

函数的实现如下:

def sprout_leaves(t, leaves):
    """Sprout new leaves containing the data in leaves at each leaf in
    the original tree t and return the resulting tree.
    """
    "*** YOUR CODE HERE ***"
    if is_leaf(t):
        return tree(label(t), [tree(leaf) for leaf in leaves])
    return tree(label(t), [sprout_leaves(branch, leaves) for branch in branches(t)])

以上实现满足评分脚本要求

python3 ok -q sprout_leaves --local
=====================================================================
Assignment: Lab 5
OK, version v1.18.1
=====================================================================

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Running tests

---------------------------------------------------------------------
Test summary
    1 test cases passed! No cases failed.
Q5: Don’t violate the abstraction barrier

这一部分没有要求写任何代码

而是要求在编写使用ADT的函数时,尽可能使用构造器和选择器,而不是对底层数据进行操作

关于抽象屏障,已在lab 04 Q6中进行过相关说明

本节要求使用脚本检测实现是否违反抽象障碍,抽象屏障的本质保证,只要正确使用构造函数和选择函数,更改ADT的实现不应影响使用该ADT的任何程序的功能。

$ python3 ok -q check_abstraction --local
=====================================================================
Assignment: Lab 5
OK, version v1.18.1
=====================================================================

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Running tests

---------------------------------------------------------------------
Test summary
    1 test cases passed! No cases failed.

Optional Questions 可选问题

Q6: Add trees

定义函数 add_trees

这个函数的输入为两个树t1t2

返回值为一个新树:

  • 这个树中某个节点的值等于t1t2对应节点的值求和
  • t1t2中某一个树中的某个特定位置上存在节点,但另一个树中不存在该节点,则新树中该节点的值等于存在该节点的树中该节点的值

样例在doctest给出:

"""
>>> numbers = tree(1,
...                [tree(2,
...                      [tree(3),
...                       tree(4)]),
...                 tree(5,
...                      [tree(6,
...                            [tree(7)]),
...                       tree(8)])])
>>> print_tree(add_trees(numbers, numbers))
2
  4
    6
    8
  10
    12
      14
    16
>>> print_tree(add_trees(tree(2), tree(3, [tree(4), tree(5)])))
5
  4
  5
>>> print_tree(add_trees(tree(2, [tree(3)]), tree(2, [tree(3), tree(4)])))
4
  6
  4
>>> print_tree(add_trees(tree(2, [tree(3, [tree(4), tree(5)])]), \
tree(2, [tree(3, [tree(4)]), tree(5)])))
4
  6
    8
    5
  5
"""

分析以下样例:

# case 1
      1                    1                    1+1
    /   \                /   \                /     \
   2     5              2     5            2+2       5+5
  / \   / \     +      / \   / \     =    /   \     /   \
 3   4 6   8          3   4 6   8       3+3   4+4  6+6  8+8
      /                  /                   /
     7                  7                  7+7

# case 2
 2        3        2+3
    +    / \   =   / \
        4   5     4   5

# case 3
  2        2          2+2
 /   +    / \   =    /   \
3        3   4     3+3    4

# case 4
     2        2          2+2
    /   +    / \        /   \
   3        3   5  =  3+3    5
  / \      /         /   \    
 4   5    4         4+4   5

课程组给出提示:可能需要使用python内置的zip函数来同时迭代多个序列

思路如下:

  1. 接收两棵树t1t2作为参数
  2. 对于根节点,将t1t2根节点的值相加,作为新树的根节点值
  3. 对于t1t2的各个子树:
    1. 使用zip函数打包两个序列,分别对相应的子树(如(t1_branch1, t2_branch1))调用add_tree(即回到第一步)得到一个子树序列
    2. t1t2的子树个数不等,取子树较多的树的分支的剩余子树
    3. 将上两步得到的两个序列合并
  4. 2.得到的值为根节点值,以3.得到的序列为子树序列构造一颗新的树,该树即所求的树

函数如下:

def add_trees(t1, t2):
    "*** YOUR CODE HERE ***"
    def get_branches(t1, t2):
        branch_t1 = branches(t1)
        branch_t2 = branches(t2)
        new_branches = [add_trees(t1_branch, t2_branch) for t1_branch, t2_branch in zip(branch_t1, branches(t2))]
        if len(branch_t1) < len(branch_t2):
            start = len(branch_t1)
            new_branches += branch_t2[start:]
        elif len(branch_t1) > len(branch_t2):
            start = len(branch_t2)
            new_branches += branch_t1[start:]
        return new_branches
    return tree(label(t1)+label(t2), get_branches(t1, t2))

以上实现可以通过脚本测评

$ python3 ok -q add_trees --local
=====================================================================
Assignment: Lab 5
OK, version v1.18.1
=====================================================================

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Running tests

---------------------------------------------------------------------
Test summary
    1 test cases passed! No cases failed.

Shakespeare and Dictionaries 莎士比亚与字典

本节将使用python内置的数据结构字典来近似莎士比亚的作品

将使用一个二元语言模型,思路如下:

  • 从某个单词开始,如单词the
  • 遍历莎士比亚所有的文本,对于每个the的实例,记录其之后的单词,并将其添加到一个列表中,称该列表为the的后继,假设已对所有单词完成该操作
  • 随机选择该列表中的一个单词,如cat,查找cat的后继,随机选择一个单词,重复进行该过程,直到遇到一个.!?结束,生成一个莎士比亚句子

在上述过程中,将查找的对象称为“后继表”,它是一个python字典,在该字典中,键是单词,而值是那些单词的后继列表

Q7: Successor Tables

本节要求实现build_successors_table函数

输入是单词列表(对应于莎士比亚文本)

输出是后继表(successors table)

注意:默认每一句的第一个单词为.的后继

示例如下:

"""
>>> text = ['We', 'came', 'to', 'investigate', ',', 'catch', 'bad', 'guys', 'and', 'to', 'eat', 'pie', '.']
>>> table = build_successors_table(text)
>>> sorted(table)
[',', '.', 'We', 'and', 'bad', 'came', 'catch', 'eat', 'guys', 'investigate', 'pie', 'to']
>>> table['to']
['investigate', 'eat']
>>> table['pie']
['.']
>>> table['.']
['We']
"""

注:共需要完成两个代码段,即两个"***YOUR CODE HERE ***"

def build_successors_table(tokens):
    """Return a dictionary: keys are words; values are lists of successors.
    """
    table = {}
    prev = '.'
    for word in tokens:
        if prev not in table:
            "*** YOUR CODE HERE ***"
            table[prev] = []
        "*** YOUR CODE HERE ***"
        table[prev] += [word]
        prev = word
    return table

函数实现满足测评脚本要求

$ python3 ok -q build_successors_table --local
=====================================================================
Assignment: Lab 5
OK, version v1.18.1
=====================================================================

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Running tests

---------------------------------------------------------------------
Test summary
    1 test cases passed! No cases failed.
Q8: Construct the Sentence

本节实现一个函数用于生成一些句子

  1. 假设有一个起始单词word
  2. 在我们的表table中查找这个单词,以找到它的后继列表
  3. 随机从这个列表中选择一个单词作为句子中的下一个单词,返回第一步,直到遇到一个终止符.,?,!

提示:要从列表中随机选择,使用import random导入Pythonrandom库,使用表达式random.choice(my_list)

doctest如下:

"""
    >>> table = {'Wow': ['!'], 'Sentences': ['are'], 'are': ['cool'], 'cool': ['.']}
    >>> construct_sent('Wow', table)
    'Wow!'
    >>> construct_sent('Sentences', table)
    'Sentences are cool.'
"""

函数实现如下:

def construct_sent(word, table):
    """Prints a random sentence starting with word, sampling from
    table.
    """
    import random
    result = ''
    while word not in ['.', '!', '?']:
        "*** YOUR CODE HERE ***"
        result += word + " "
        word = random.choice(table[word])
    return result.strip() + word

以上实现满足测评脚本要求

$ python3 ok -q construct_sent --local
=====================================================================
Assignment: Lab 5
OK, version v1.18.1
=====================================================================

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Running tests

---------------------------------------------------------------------
Test summary
    1 test cases passed! No cases failed.

最后,课程组提供了一些函数将Q7Q8的实现结合了起来,使用一些现实数据来运行函数

以下代码段返回一个包含莎士比亚所有单词的列表

def shakespeare_tokens(path='shakespeare.txt', url='http://composingprograms.com/shakespeare.txt'):
    """Return the words of Shakespeare's plays as a list."""
    import os
    from urllib.request import urlopen
    if os.path.exists(path):
        return open('shakespeare.txt', encoding='ascii').read().split()
    else:
        shakespeare = urlopen(url)
        return shakespeare.read().decode(encoding='ascii').split()

以下两行代码运行了上述函数并建立了一个后继列表

tokens = shakespeare_tokens()
table = build_successors_table(tokens)

课程组实现了一个利用后继列表生成句子的函数

>>> def sent():
...     return construct_sent('The', table)
>>> sent()
" The plebeians have done us must be news-cramm'd."
>>> sent()
" The ravish'd thee , with the mercy of beauty!"
>>> sent()
" The bird of Tunis , or two white and plucker down with better ; that's God's sake."

上述函数生成的句子均以The打头,对上述实现做出一定的修改,使得句子以一个随机的单词开头,函数如下:

def random_sent():
    import random
    return construct_sent(random.choice(table['.']), table)

上述函数可以随机生成一个莎士比亚句子

>>> random_sent()
' Long live by thy name , then , Dost thou more angel , good Master Deep-vow , And tak'st more ado but following her , my sight Of speaking false!'
>>> random_sent()
' Yes , why blame him , as is as I shall find a case , That plays at the public weal or the ghost.'
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值