2.9 递归对象
若一个对象的某个属性值为所属同一个类的某个对象,则称其为递归对象。
问题:这个属性对象可以是自己吗?还是必须得是其他对象呢?
答:可以是自己,但这就是无限递归了。
2.9.1 链表(Linked List Class)
正如之间所介绍的,链表由两部分组成,第一部分是一个元素,第二部分是一个子链表。子链表又由两个部分组成……这就很有递归的味道了。直到empty,递归结束。
想讨论链表的递归,总得有链表供我们使用吧。链表满足序列的基本特性——有限长度与元素检索。下面我们通过代码来创建链表类与实现它的基本功能。
>>> class Link:
"""A linked list with a first element and the rest."""
empty = ()
def __init__(self, first, rest=empty):
assert rest is Link.empty or isinstance(rest, Link)
self.first = first
self.rest = rest
def __getitem__(self, i):
if i == 0:
return self.first
else:
return self.rest[i-1]
def __len__(self):
return 1 + len(self.rest)
>>> s = Link(3, Link(4, Link(5)))
>>> len(s)
3
>>> s[1]
4
链表类或许还不够完善,当创建完一个链表后我们只有检索单个元素的操作,却很难再整体观察该链表的每个元素。需要做一些改进,加一个新的方法。
>>> def link_expression(s):
"""Return a string that would evaluate to s."""
if s.rest is Link.empty:
rest = ''
else:
rest = ', ' + link_expression(s.rest)
return 'Link({0}{1})'.format(s.first, rest)
>>> link_expression(s)
'Link(3, Link(4, Link(5)))'
这是个好函数,方便实用。如果把它与Link类的__repr__方法绑定起来就更方便啦。
>>> Link.__repr__ = link_expression
>>> s
Link(3, Link(4, Link(5)))
到此我们就基本 完成了Link类的实现。
一个函数里面可以定义另一个函数(闭包),列表里面可以有列表。链表也具有闭包的特性,即链表的第一个元素可以也是链表。
>>> s_first = Link(s, Link(6))
>>> s_first
Link(Link(3, Link(4, Link(5))), Link(6))
>>> len(s_first)
2
>>> len(s_first[0])
3
>>> s_first[0][2]
5
由此看来关于链表递归的深度还是值得讨论的,不过先放一放,暂时把目光放在一些更简单问题上。
递归和递归对象简直是天作之合。我们可以用递归实现很对关于链表的操作。其实,在创建链表类时就已经用到了递归,不过我们的脚步并不止于此。对于链表,我想写一个和list一样的extend函数,用于把两个链表连接起来。
最简单的思路是,把前一个链表的empty改为要加上去的链表。不过目前我们没有直接抵达empty的方法。可以重新从递归的角度思考一下,这个函数该怎么写。
>>> def extend_link(s, t):
if s is Link.empty:
return t
else:
return Link(s.first, extend_link(s.rest, t))
>>> extend_link(s, s)
Link(3, Link(4, Link(5, Link(3, Link(4, Link(5))))))
>>> Link.__add__ = extend_link
>>> s + s
Link(3, Link(4, Link(5, Link(3, Link(4, Link(5))))))
至此,我们已经发现了链表其实和列表特别像。他们都是有限长度,可以进行元素检索,可以extend。自然,我们也能对列表的map函数和filter函数进行复刻,来做两个属于链表的函数。
那么该怎么写这样的函数呢?递归似乎是万能之法。
>>> def map_link(f, s):
if s is Link.empty:
return s
else:
return Link(f(s.first), map_link(f, s.rest))
>>> map_link(square, s)
Link(9, Link(16, Link(25)))
map函数完成了,对每个可操作元素调用f,而后产生一个新的链表,完美。那么filter函数呢?
>>> def filter_link(f, s)
if s is Link.empty:
return s
else:
if f(f.first):
return Link(s.fisrt, filter(f, s.rest))
else:
return filter(f, s.rest)
>>> odd = lambda x: x % 2 == 1
>>> map_link(square, filter_link(odd, s))
Link(9, Link(25))
>