• 沒有找到結果。

9.5.3 __getattr__ 、 __setattr__ 等方法

9.8 八皇后问题

9.8.6 递归条件

10 11 12 13 14

def queens(num, state):

if len(state) == num-1:

for pos in range(num):

if not conflict(state, pos):

yield pos

这段代码的意思是,如果只剩下最后一个皇后没有放好,就遍历所有可能的位置,并返回那 些不会引发冲突的位置。参数num为皇后总数,而参数state是一个元组,包含已放好的皇后的位 置。例如,假设总共有4个皇后,而前3个皇后的位置分别为1、3和0,如图9-1所示。(现在不用 关心白色的皇后。)

图9-1 在一个4行4列的棋盘上放置4个皇后

从该图可知,每个皇后都占据一行,而皇后的位置是从0开始编号的(Python中通常如此)。

>>> list(queens(4, (1, 3, 0))) [2]

代码的效果很好。这里使用list旨在让生成器生成所有的值。在这个示例中,只有一个位置 符合条件。在图9-1中,在这个位置放置了一个白色皇后。(请注意,颜色没有什么特殊含义,不 是程序的一部分。)

9.8.6 递归条件

现在来看看这个解决方案的递归部分。处理好基线条件后,可在递归条件中假设来自更低层

级(编号更大的皇后)的结果都是正确的。因此,只需在函数queens的前述实现中给if语句添加 一个else子句。

你希望递归调用返回什么样的结果呢?你希望它返回当前行下面所有皇后的位置,对吧?假 设位置是以元组的方式返回的,因此需要修改基线条件,使其返回一个(长度为1的)元组,但 这将在后面处理。

因此,对于递归调用,向它提供的是由当前行上面的皇后位置组成的元组。对于当前皇后的 每个合法位置,递归调用返回的是由下面的皇后位置组成的元组。为了让这个过程不断进行下去,

只需将当前皇后的位置插入返回的结果开头,如下所示:

...

else:

for pos in range(num):

if not conflict(state, pos):

for result in queens(num, state + (pos,)):

yield (pos,) + result

这里的for pos和if not conflict部分与前面相同,因此可以稍微简化一下代码。另外,还

可给参数指定默认值。

def queens(num=8, state=()):

for pos in range(num):

if not conflict(state, pos):

if len(state) == num-1:

yield (pos,) else:

for result in queens(num, state + (pos,)):

yield (pos,) + result

如果你觉得这些代码难以理解,用自己的话来描述其作用可能会有所帮助。另外,你可能还

记得(pos,)中的逗号必不可少(不能仅用圆括号将pos括起),这样得到的才是元组。

生成器queens提供了所有的解(即所有合法的皇后位置组合)。

>>> list(queens(3)) []

>>> list(queens(4))

[(1, 3, 0, 2), (2, 0, 3, 1)]

>>> for solution in queens(8):

... print solution ...

(0, 4, 7, 5, 2, 6, 1, 3) (0, 5, 7, 2, 6, 3, 1, 4) ...

(7, 2, 0, 5, 1, 4, 6, 3) (7, 3, 0, 2, 5, 1, 6, 4)

>>>

如果运行queens时将参数num设置为8,将快速显示大量的解。下面看看有多少个解。

>>> len(list(queens(8))) 92

1 2 3 4 5

15 6 7 8 9 10 11 12 13 14 9.8.7 扫尾工作

结束本节之前,可以让输出更容易理解些。在任何情况下,清晰的输出都是好事,因为这让 查找bug等工作更容易。

def prettyprint(solution):

def line(pos, length=len(solution)):

return '. ' * (pos) + 'X ' + '. ' * (length-pos-1) for pos in solution:

print(line(pos))

请注意,我在prettyprint中创建了一个简单的辅助函数。之所以将它放在prettyprint中,

是因为我认为在其他地方都用不到它。下面随机地选择一个解,并将其打印出来,以确定它是正 确的。

>>> import random

>>> prettyprint(random.choice(list(queens(8)))) . . . X . .

. X . . . . . . X . X . . . . . . X . . . . . . . X . . . . X . . . . . X . . .

图9-2显示了这个解。

图9-2 八皇后问题的众多解之一

9.9 小结

本章介绍的内容很多,下面来总结一下。

新式类和旧式类:Python类的工作方式在不断变化。较新的Python 2版本有两种类,其中 旧式类正在快速退出舞台。新式类是Python 2.2引入的,提供了一些额外的功能,如支持

函数super和property,而旧式类不支持。要创建新式类,必须直接或间接地继承object

或设置__metaclass__。

魔法方法:Python中有很多特殊方法,其名称以两个下划线开头和结尾。这些方法的功能 各不相同,但大都由Python在特定情况下自动调用。例如__init__是在对象创建后调用的。

构造函数:很多面向对象语言中都有构造函数,对于你自己编写的每个类,都可能需要 为它实现一个构造函数。构造函数名为__init__,在对象创建后被自动调用。

重写:类可重写其超类中定义的方法(以及其他任何属性),为此只需实现这些方法即可。

要调用被重写的版本,可直接通过超类调用未关联版本(旧式类),也可使用函数super 来调用(新式类)。

序列和映射:要创建自定义的序列或映射,必须实现序列和映射协议指定的所有方法,

其中包括__getitem__和__setitem__等魔法方法。通过从list(或UserList)和dict(或

UserDict)派生,可减少很多工作量。

迭代器:简单地说,迭代器是包含方法__next__的对象,可用于迭代一组值。没有更多的 值可供迭代时,方法__next__应引发StopIteration异常。可迭代对象包含方法__iter__, 它返回一个像序列一样可用于for循环中的迭代器。通常,迭代器也是可迭代的,即包含 返回迭代器本身的方法__iter__。

生成器:生成器的函数是包含关键字yield的函数,它在被调用时返回一个生成器,即一 种特殊的迭代器。要与活动的生成器交互,可使用方法send、throw和close。

八皇后问题:八皇后问题是个著名的计算机科学问题,使用生成器可轻松地解决它。这 个问题要求在棋盘上放置8个皇后,并确保任何两个皇后都不能相互攻击。

9.9.1 本章介绍的新函数

函 数 描 述

iter(obj) 从可迭代对象创建一个迭代器

next(it) 让迭代器前进一步并返回下一个元素

property(fget, fset, fdel, doc) 返回一个特性;所有参数都是可选的

super(class, obj) 返回一个超类的关联实例

调用iter和super时,还可提供这里没有列出的其他参数,更详细的信息请参阅标准Python

文档。

1 2 3 4 5

15 6 7 8 9 10 11 12 13 14 9.9.2 预告

至此,你学习了Python语言的大部分知识,但为何本书后面还有这么多章呢?因为需要学习 的知识还有很多,大都是关于Python如何以各种方式与外部联系的。另外,还有测试、扩展、打 包和一些具体项目。本书还远没有到结束的时候。