下载 lab14.zip 。在该压缩包中,你会发现本实验中问题的起始文件,以及 Ok 自动评分器的副本。
这个实验有很多文件。记住要在 lab14.scm 中写 Scheme 问题,在 lab14.lark 中写 BNF 问题,在 lab14.py 中写所有其他问题。
实现 split-at ,它接收一个列表 lst 和一个非负数 n 作为输入,并返回一对 new ,使得 (car new) 是 lst 的前 n 个元素, (cdr new) 是 lst 的其余元素。如果 n 大于 lst 的长度, (car new) 应该是 lst , (cdr new) 应该是 nil 。
scm> (car (split-at '(2 4 6 8 10) 3))
(2 4 6)
scm> (cdr (split-at '(2 4 6 8 10) 3))
(8 10)
(define (split-at lst n)
  'YOUR-CODE-HERE
)
使用 Ok 来测试你的代码:
python3 ok -q split-at
编写一个函数 filter-odd ,它接收一个 tree 数据抽象,并返回一个新的 tree ,所有的偶数 label 都被替换为 nil 。
考虑使用
map过程将一个单参数函数应用于一个列表。
下面是本学期我们一直在使用的 Tree 类的 Scheme-ified 数据抽象。
; Constructs tree given label and list of branches
(tree label branches)
; Returns the label of the tree
(label t)
; Returns the list of branches of the given tree
(branches t)
(define (filter-odd t)
    'YOUR-CODE-HERE
)
使用 Ok 来测试你的代码:
python3 ok -q filter_odd
实现 swap ,它接收一个表达式 expr ,代表对某个过程的调用,如果第二个操作数的值大于第一个操作数的值,则返回相同的表达式,并将其前两个操作数对换。否则,它应该只返回原始表达式。例如, (swap '(- 1 (+ 3 5) 7)) 应该返回表达式 (- (+ 3 5) 1 7) ,因为 1 的值是 1 , (+ 3 5) 的值是 8 ,而且 8 > 1 。在执行过程中,前两个操作数之后的任何操作数都不应该被计算,它们应该在最后的表达式中保持不变。你可以假设每个操作数都为一个数字,并且在 expr 中总是有至少两个操作数。你可以考虑在提供的程序之外使用 let 表达式来帮助简化你的代码。
(define (cddr s)
  (cdr (cdr s))
)
(define (cadr s)
  (car (cdr s))
)
(define (caddr s)
  (car (cddr s))
)
(define (swap expr)
    'YOUR-CODE-HERE
)
使用 Ok 来测试你的代码:
python3 ok -q swap
编写一个正则表达式,解析字符串并返回它是否包含美国邮寄地址的第一行。
美国的邮寄地址通常包含一个街区号码,这是一个由 3-5 位数字组成的序列,后面是一个街道名称。街道名称可以由多个单词组成,但总是以街道类型的缩写结束,其本身是一个由 2-5 个英文字母组成的序列。街道名称也可以选择以红线方向(“N”、“E”、“W”、“S”)开始。一切都应适当地大写。
正确的大写意味着每个名字的第一个字母都是大写的。像 “WeirdCApitalization”这样的匹配是可以的。
参见 doctests 中的一些例子。
def address_oneline(text):
    """
    Finds and returns if there are expressions in text that represent the first line
    of a US mailing address.
    >>> address_oneline("110 Sproul Hall, Berkeley, CA 94720")
    True
    >>> address_oneline("What's at 39177 Farwell Dr? Is there a 39177 Nearwell Dr?")
    True
    >>> address_oneline("I just landed at 780 N McDonnell Rd, and I need to get to 1880-ish University Avenue. Help!")
    True
    >>> address_oneline("123 Le Roy Ave")
    True
    >>> address_oneline("110 Unabbreviated Boulevard")
    False
    >>> address_oneline("790 lowercase St")
    False
    """
    block_number = r'___'
    cardinal_dir = r'___' # whitespace is important!
    street = r'___'
    type_abbr = r'___'
    street_name = f"{cardinal_dir}{street}{type_abbr}"
    return bool(re.search(f"{block_number} {street_name}", text))
使用 Ok 来测试你的代码:
python3 ok -q address_oneline
考虑一下这个用于 Pycombinator 的 BNF 语法的尝试,这个语法支持 Python 的一个子集的功能。具体来说,它能够解析任何带有 Python 算术运算符的表达式。该语法的具体内容如下:
?start: pycomb_expression
pycomb_expression: func "(" arg ("," arg)* ")"
arg: pycomb_expression | NUMBER
func: FUNCNAME
FUNCNAME: "add" | "mul" | "sub"
%ignore " "
%import common.NUMBER
让我们通过几个问题来了解和修改这个 BNF 的功能。
使用 Ok 来测试你对知识的理解,为以下每个“PyCombinator会做什么”的问题选择最佳答案:
python3 ok -q wwpd-bnf -u
请确保提交该实验:
python3 ok --submit
以下问题不是本实验的学分要求,但可以帮助你为期末考试做准备。
编写一个函数,对一棵 Tree t 进行修剪, t 及其分支总是有零或两个分支。对于有两个分支的树,通过保留具有较小标签值的分支,将分支的数量从两个减少到一个。对零分支的树不做任何处理。
按照你选择的方向(自上而下或自下而上)对树进行修剪。结果应该是一棵线性树。
def prune_min(t):
    """Prune the tree mutatively.
    >>> t1 = Tree(6)
    >>> prune_min(t1)
    >>> t1
    Tree(6)
    >>> t2 = Tree(6, [Tree(3), Tree(4)])
    >>> prune_min(t2)
    >>> t2
    Tree(6, [Tree(3)])
    >>> t3 = Tree(6, [Tree(3, [Tree(1), Tree(2)]), Tree(5, [Tree(3), Tree(4)])])
    >>> prune_min(t3)
    >>> t3
    Tree(6, [Tree(3, [Tree(1)])])
    """
    "*** YOUR CODE HERE ***"
使用 Ok 来测试你的代码:
python3 ok -q prune_min
定义函数 add_trees ,它接收两棵树并返回一棵新的树,其中第一棵树的每个对应节点都被添加到第二棵树的节点中。如果某个位置的节点出现在一棵树上,但没有出现在另一棵树上,那么它也应该出现在新树上。
提示: 你可能想使用内置的 zip 函数来一次迭代多个序列。
def add_trees(t1, t2):
    """
    >>> numbers = Tree(1,
    ...                [Tree(2,
    ...                      [Tree(3),
    ...                       Tree(4)]),
    ...                 Tree(5,
    ...                      [Tree(6,
    ...                            [Tree(7)]),
    ...                       Tree(8)])])
    >>> print(add_trees(numbers, numbers))
    2
      4
        6
        8
      10
        12
          14
        16
    >>> print(add_trees(Tree(2), Tree(3, [Tree(4), Tree(5)])))
    5
      4
      5
    >>> print(add_trees(Tree(2, [Tree(3)]), Tree(2, [Tree(3), Tree(4)])))
    4
      6
      4
    >>> print(add_trees(Tree(2, [Tree(3, [Tree(4), Tree(5)])]), \
    Tree(2, [Tree(3, [Tree(4)]), Tree(5)])))
    4
      6
        8
        5
      5
    """
    "*** YOUR CODE HERE ***"
使用 Ok 来测试你的代码:
python3 ok -q add_trees
让我们来实现一个名为选举的游戏。在这个游戏中,两个玩家竞争,试图赢得最多的选票。两个玩家都从 0 票和 100 人气开始。
两个玩家交替轮流,由第一个玩家开始。每一回合,当前玩家选择一个行动。有两种类型的行动:
p1 ,另一玩家的人气为 p2 ,那么玩家获得 50 人气的概率为 max(0.1, p1 / (p1 + p2)) 注意, max 导致概率永远不会低于 0.1 。p1 ,而另一玩家的人气为 p2 ,则该玩家获得 p1 // 10 票和人气,另一玩家失去 p2 // 10 人气。当一个玩家的票数达到 50 票,或者总共进行了 10 个回合(每个玩家进行了 5 个回合)之后,游戏就会结束。游戏结束时,谁的票数多,谁就是赢家!
首先,我们来实现 Player 类。填写 debate 和 speech 的方法,这些方法接收了另一个 Player other ,并实现了上面详述的正确行为。这里有两件额外的事情要记住:
debate 方法中,你应该调用所提供的 random 函数,该函数返回一个介于 0 和 1 之间的随机浮点数。如果该随机数小于上述的概率,玩家应该获得 50 的人气,否则会失去 50 的人气。### Phase 1: The Player Class
class Player:
    """
    >>> random = make_test_random()
    >>> p1 = Player('Hill')
    >>> p2 = Player('Don')
    >>> p1.popularity
    100
    >>> p1.debate(p2)  # random() should return 0.0
    >>> p1.popularity
    150
    >>> p2.popularity
    100
    >>> p2.votes
    0
    >>> p2.speech(p1)
    >>> p2.votes
    10
    >>> p2.popularity
    110
    >>> p1.popularity
    135
    """
    def __init__(self, name):
        self.name = name
        self.votes = 0
        self.popularity = 100
    def debate(self, other):
        "*** YOUR CODE HERE ***"
    def speech(self, other):
        "*** YOUR CODE HERE ***"
    def choose(self, other):
        return self.speech
使用 Ok 来测试你的代码:
python3 ok -q Player
现在,实现 Game 类。填写 play 方法,它应该在两个玩家之间交替进行,从 p1 开始,让每个玩家一次轮到一个。 Player 类中的 choose 方法返回应被调用的方法,可以是 debate ,也可以是 speech ,以执行该动作。
此外,填写 winner 方法,它应该返回拥有更多选票的玩家,如果玩家打成平手,则返回 None 。
### Phase 2: The Game Class
class Game:
    """
    >>> p1, p2 = Player('Hill'), Player('Don')
    >>> g = Game(p1, p2)
    >>> winner = g.play()
    >>> p1 is winner
    True
    """
    def __init__(self, player1, player2):
        self.p1 = player1
        self.p2 = player2
        self.turn = 0
    def play(self):
        while not self.game_over():
            "*** YOUR CODE HERE ***"
        return self.winner()
    def game_over(self):
        return max(self.p1.votes, self.p2.votes) >= 50 or self.turn >= 10
    def winner(self):
        "*** YOUR CODE HERE ***"
使用 Ok 来测试你的代码:
python3 ok -q Game
Player 类中的 choose 方法很无聊,因为它总是返回 speech 方法。让我们来实现两个继承自 Player 的新类,但有更有趣的 choose 方法。
在 AggressivePlayer 类中实现 choose 方法,如果玩家的人气小于或等于其他玩家 other 的人气,则返回 debate 方法,否则返回 speech 。同时在 CautiousPlayer 类中实现 choose 方法,如果玩家的人气为 0 ,则返回 debate 方法,反之则返回 speech 。
### Phase 3: New Players
class AggressivePlayer(Player):
    """
    >>> random = make_test_random()
    >>> p1, p2 = AggressivePlayer('Don'), Player('Hill')
    >>> g = Game(p1, p2)
    >>> winner = g.play()
    >>> p1 is winner
    True
    """
    def choose(self, other):
        "*** YOUR CODE HERE ***"
class CautiousPlayer(Player):
    """
    >>> random = make_test_random()
    >>> p1, p2 = CautiousPlayer('Hill'), AggressivePlayer('Don')
    >>> p1.popularity = 0
    >>> p1.choose(p2) == p1.debate
    True
    >>> p1.popularity = 1
    >>> p1.choose(p2) == p1.debate
    False
    """
    def choose(self, other):
        "*** YOUR CODE HERE ***"
使用 Ok 来测试你的代码:
python3 ok -q AggressivePlayer
python3 ok -q CautiousPlayer
实现 intersection(lst_of_lsts) ,它接收一个列表,并返回一个在 lst_of_lsts 的所有列表中出现的独特元素的列表。如果没有数字出现在所有的列表中,则返回空列表。你可以假设 lst_of_lsts 至少包含一个列表。
def intersection(lst_of_lsts):
    """Returns a list of distinct elements that appear in every list in
    lst_of_lsts.
    >>> lsts1 = [[1, 2, 3], [1, 3, 5]]
    >>> intersection(lsts1)
    [1, 3]
    >>> lsts2 = [[1, 4, 2, 6], [7, 2, 4], [4, 4]]
    >>> intersection(lsts2)
    [4]
    >>> lsts3 = [[1, 2, 3], [4, 5], [7, 8, 9, 10]]
    >>> intersection(lsts3)         # No number appears in all lists
    []
    >>> lsts4 = [[3, 3], [1, 2, 3, 3], [3, 4, 3, 5]]
    >>> intersection(lsts4)         # Return list of distinct elements
    [3]
    """
    elements = []
    "*** YOUR CODE HERE ***"
    return elements
使用 Ok 来测试你的代码:
python3 ok -q intersection
编写一个列表,它将创建一副牌,给定一个花色列表 suits 和一个等级列表 ranks 。列表中的每个元素都是一张牌,它由一个形式为 [suit, rank] 的 2 元列表表示。
def deck(suits, ranks):
    """Creates a deck of cards (a list of 2-element lists) with the given
    suits and ranks. Each element in the returned list should be of the form
    [suit, rank].
    >>> deck(['S', 'C'], [1, 2, 3])
    [['S', 1], ['S', 2], ['S', 3], ['C', 1], ['C', 2], ['C', 3]]
    >>> deck(['S', 'C'], [3, 2, 1])
    [['S', 3], ['S', 2], ['S', 1], ['C', 3], ['C', 2], ['C', 1]]
    >>> deck([], [3, 2, 1])
    []
    >>> deck(['S', 'C'], [])
    []
    """
    "*** YOUR CODE HERE ***"
    return ______
使用 Ok 来测试你的代码:
python3 ok -q deck
帕斯卡尔三角形也许是你熟悉的,下图显示了前五行的情况。

每一个方格都是它上面两个方格的总和(如箭头所示,这里的数值 4 ),除非它上面没有两个方格,在这种情况下,它的数值是 1 。
给出一个代表帕萨尔三角形中某一行的链表,返回一个代表它下面一行的链表。

def pascal_row(s):
    """
    >>> a = Link.empty
    >>> for _ in range(5):
    ...     a = pascal_row(a)
    ...     print(a)
    <1>
    <1 1>
    <1 2 1>
    <1 3 3 1>
    <1 4 6 4 1>
    """
    "*** YOUR CODE HERE ***"
使用 Ok 来测试你的代码:
python3 ok -q pascal_row