分享一道伯克利 CS 61A 关于高阶函数的一道Python作业题(2)

这里,我给出个人对于上一篇文章中题目的一种解答。

首先,对于 one two这两个函数的定义,这个对于很多人来说,应该是没有什么难度的。根据successor的定义,我们可以得到下面两个函数的定义

def zero(f):
    return lambda x: x

def successor(n):
    return lambda f: lambda x: f(n(f)(x))

def one(f):
    """Church numeral 1: same as successor(zero)"""
    "*** YOUR CODE HERE ***"
    return lambda x: f(zero(f)(x))

def two(f):
    """Church numeral 2: same as successor(successor(zero))"""
    "*** YOUR CODE HERE ***"
    return lambda x: f(one(f)(x))

three = successor(two)



好了,接下来是真正的实验——church_to_int(n)函数。这里,有两种可能: 从 0 到 n 和 从 n 到 0。两种方法我都试过,不过从 0 到n 貌似出不来结果,如果你的解答是从 0 到 n 算出来的,麻烦告诉我一声。

那么,接下来让我们从twozero进行分析(当然,你可以选择直接分析successor,如果你没被他的定义搞晕的话)。

首先我们应该明白,two(f)返回的是一个函数,该函数有一个formal parameter x。然后,让我们更进一步,假定有这么一个变量x存在,然后two(f)(x) == Two。同样的,one(f)(x) == One, zero(f)(x) == Zero(注意我这里用了大写)。

那么将有

two(f)(x) ->  f(One)
one(f)(x) ->  f(Zero)

到了这里,是不是觉得,好像发现了什么呢?至少,很有规律,不是吗?


对于每一个 Church number function,我们都将比他小1的函数的返回值,作为实参,再调用了一次f函数。也就是说,对于一个数值为n的函数,f会被调用n次(一般情况下是如此,后面我们将挑战这一权威)。说到这里,答案其实已经浮出了水面,如果你没有忘记题目额外给出的一个increment函数的话。

def increment(x):
    return x + 1

def church_to_int(n):
    """Convert the Church numeral n to a Python integer. >>> church_to_int(zero) 0 >>> church_to_int(one) 1 >>> church_to_int(two) 2 >>> church_to_int(three) 3 """
    "*** YOUR CODE HERE ***"
    return n(increment)(0)

是不是很惊讶,答案居然是如此的简洁(老实说,个人写出这个一行代码时,也是觉得挺震撼的,题目的设计者真是个天才)。每次调用increment的时候,都会 +1,我们执行了increment n次,也就得到了我们想要的int值。




对于add_church函数,直觉上,想要从 mn而得到m+n,我们只需要简单地从n使用successor递增m步即可(当然,你也可以先判断哪一个比较小,这里暂时忽略这个问题)。于是,便有了下面这个解答:

def add_church(m, n):
    """Return the Church numeral for m + n, for Church numerals m and n. >>> church_to_int(add_church(two, three)) 5 >>> church_to_int(add_church(zero, three)) 3 """
    "*** YOUR CODE HERE ***"
    num_m = church_to_int(m)
    while num_m > 0:
        n = successor(n)
        num_m -= 1
    return n
    # there is a better solution below

不知道你对这个答案满意度如何,我呢,总觉得有些别扭。这里我们居然用了5 行代码!5这个数字也许不是很大,但比起上一个问题中的1行代码,确实有点太多了。不过,这里我先放一放,后面我们再来讨论这个问题。




好啦,先放下add_church这么一个疙瘩,我们来看看mul_church

在告诉你答案前,我觉得,还是先来复习一下小学乘法:

2×3 表示 2 个 3 或 3 个 2

不管怎么样,还是先急着这个吧。

在前面我们实现church_to_int(f)时,我们在每次调用f时执行了+1操作,最后得到了church 函数对应的 整数值。这里,我们可以如法炮制。不管,现在每次增加的是 m而不是1(这里判断m, n那个比较小没有任何意义)。沿着这个思路,我们可能会写出:

def mul_church(m, n):
    num_m = church_to_int(m)
    add_m = lambda x: x + num_m
    return n(add_m)(0)

很遗憾,这个是错的。我们需要的是一个 church 函数,而不是一个数字。不过,这是一个很好的开端。我们需要返回一个高阶函数,该函数接收一个函数作为实参且church_to_int利用该传入的实参计算对应的整数值。

接下来需要一个小小的跳跃。以我们church_to_int的实现来看,难道你传一个increment给我,我就得乖乖用他来一步一步加1吗?!为什么我们不能自作主张,一步加m呢?!(还记得上面说过的吗? m×n 就是 n 个 m 相加)根据这个思想,就有了下面这个解答,从解决问题的思想上,和上面的解答其实是一样的

def mul_church(m, n):
    """Return the Church numeral for m * n, for Church numerals m and n. >>> four = successor(three) >>> church_to_int(mul_church(two, three)) 6 >>> church_to_int(mul_church(three, four)) 12 >>> church_to_int(successor(mul_church(three, four))) 13 """
    "*** YOUR CODE HERE ***"
    num_m = church_to_int(m)
    add_m = lambda x: x + num_m
    return lambda f: n(add_m)
    # or just
    # return lambda f: n(lambda x: x + num_m)




现在,让我们回到前面的 add_church(m, n)。上面给出的答案是,从 n开始,一步一步,得到了 m + n。但是,不管怎么说,这种做法总是不够优雅。得益于上面对mul_church(m, n)的思考,有这么一个想法,我们能够一步就将m加到n呢?答案是,可以。

def add_church(m, n):
    """Return the Church numeral for m + n, for Church numerals m and n. >>> church_to_int(add_church(two, three)) 5 >>> church_to_int(add_church(zero, three)) 3 """
    "*** YOUR CODE HERE ***"
    num_m = church_to_int(m)
    return lambda f: lambda x: num_m + n(f)(x)

虽然较之前面的答案更为的复杂,但个人觉得,也还算优雅。这里需要注意的一点是,lambda f: lambda x: n(f)(x)就等价于 n。当我们计算其对应的整数值时,加上了额外的 num_m,于是也就得到了 m+n对应的整数值。




是不是觉得很兴奋?非常的有趣,不是吗?到了这里,综合我的解说和你的理解,我相信,你有能力做出最后的 pow_church(m, n)。来吧,尝试一下,不然我可就只能独享这大餐了。




你可能感兴趣的:(python)