Arcade-游戏编程教程-四-

203 阅读59分钟

Arcade 游戏编程教程(四)

原文:Program Arcade Games

协议:CC BY-NC-SA 4.0

十四、精灵简介

我们的游戏需要支持处理碰撞的物体。球从球拍上弹开,激光束击中外星人,或者我们最喜欢的角色收集硬币。所有这些例子都需要碰撞检测。

pygame 库支持精灵。精灵是一个二维图像,是更大的图形场景的一部分。通常,精灵是场景中的某种对象,可以与汽车、青蛙或小水管工进行交互。

A978-1-4842-1790-0_14_Figa_HTML.jpg

最初,视频游戏控制台内置了对小精灵的硬件支持。现在不再需要这种专门的硬件支持,但我们仍然使用术语 sprite。

基本精灵和碰撞

让我们逐步完成一个使用精灵的示例程序。这个例子展示了如何创建一个黑色方块的屏幕,并使用一个由鼠标控制的红色方块来收集它们,如下图所示。该计划保持分数多少块已经收集。此示例的代码可以在以下位置找到:

ProgramArcadeGames.com/python_examples/f.php?file=sprite_collect_blocks.py

A978-1-4842-1790-0_14_Figb_HTML.jpg

精灵游戏示例

我们程序的前几行像我们做过的其他游戏一样开始(为了清楚起见添加了行号):

001 import pygame

002 import random

003

004 # Define some colors

005 BLACK = (  0,   0,   0)

006 WHITE = (255, 255, 255)

007 RED   = (255,   0,   0)

pygame 库是为第 1 行的 sprite 支持而导入的。随机库被导入用于第 2 行上的块的随机放置。颜色的定义在第 5-7 行是标准的;这个例子中没有什么新的东西。

009 class Block(pygame.sprite.Sprite):

010     """

011     This class represents the ball.

012     It derives from the "Sprite" class in pygame.

013     """

第 9 行开始定义Block类。注意,在第 9 行,这个类是Sprite类的子类。pygame.sprite.指定了库和包,这将在第十五章中讨论。Sprite类的所有默认功能现在都将成为Block类的一部分。

015     def __init__(self, color, width, height):

016         """ Constructor. Pass in the color of the block,

017         and its x and y position. """

018

019         # Call the parent class (Sprite) constructor

020         super().__init__()

第 15 行的Block类的构造函数像任何其他构造函数一样为self接受一个参数。它还接受定义对象颜色、高度和宽度的参数。

调用Sprite中的父类构造函数来允许精灵初始化是很重要的。这是在第 20 行完成的。

022         # Create an image of the block, and fill it with a color.

023         # This could also be an image loaded from the disk.

024         self.image = pygame.Surface([width, height])

025         self.image.fill(color)

第 24 行和第 25 行创建了最终将出现在屏幕上的图像。第 24 行创建了一个空白图像。第 25 行用黑色填充它。如果程序需要黑色方块以外的东西,这些是要修改的代码行。

例如,看看下面的代码:

def __init__(self, color, width, height):

"""

Ellipse Constructor. Pass in the color of the ellipse,

and its size

"""

# Call the parent class (Sprite) constructor

super().__init__()

# Set the background color and set it to be transparent

self.image = pygame.Surface([width, height])

self.image.fill(WHITE)

self.image.set_colorkey(WHITE)

# Draw the ellipse

pygame.draw.ellipse(self.image, color, [0, 0, width, height])

如果上面的代码被替换,那么一切都将是省略号的形式。第 29 行绘制椭圆,第 26 行将白色变成透明色,这样背景就显示出来了。这与第十二章中使用的使图像的白色背景透明的概念相同。

def __init__(self):

""" Graphic Sprite Constructor. """

# Call the parent class (Sprite) constructor

super().__init__()

# Load the image

self.image = pygame.image.load("player.png").convert()

# Set our transparent color

self.image.set_colorkey(WHITE)

如果需要位图图形,替换上面的代码行将加载一个图形(第 22 行)并将白色设置为透明背景色(第 25 行)。在这种情况下,sprite 的尺寸将自动设置为图形尺寸,不再需要传递它们。看看第 15 行为什么不再有这些参数。

不管我们有什么样的精灵,在构造函数中还需要一行重要的代码:

027         # Fetch the rectangle object that has the dimensions of the image

028         # image.

029         # Update the position of this object by setting the values

030         # of rect.x and rect.y

031         self.rect = self.image.get_rect()

属性rect是一个变量,它是 pygame 提供的Rect类的一个实例。矩形代表精灵的尺寸。这个 rectangle 类具有可以设置的 x 和 y 属性。Pygame 将在 x 和 y 属性所在的地方绘制精灵。所以要移动这个精灵,程序员需要设置mySpriteRef.rect.xmySpriteRef.rect.y,其中mySpriteRef是指向精灵的变量。

我们已经完成了Block类。是时候进入初始化代码了。

033 # Initialize pygame

034 pygame.init()

035

036 # Set the height and width of the screen

037 screen_width = 700

038 screen_height = 400

039 screen = pygame.display.set_mode([screen_width, screen_height])

上面的代码初始化 pygame 并为游戏创建一个窗口。这里没有任何来自其他 pygame 程序的新内容。

041 # This is a list of ’sprites.’ Each block in the program is

042 # added to this list. The list is managed by a class called ’Group.’

043 block_list = pygame.sprite.Group()

044

045 # This is a list of every sprite.

046 # All blocks and the player block as well.

047 all_sprites_list = pygame.sprite.Group()

和精灵一起工作的一个主要优势是能够和他们一起分组工作。如果精灵在一个组中,我们可以用一个命令来绘制和移动所有的精灵。我们还可以检查整个组的精灵碰撞。

上面的代码创建了两个列表。变量all_sprites_list将包含游戏中的每一个精灵。这个列表将被用来绘制所有的精灵。变量block_list保存玩家可以碰撞的每个物体。在这个例子中,它将包括游戏中除玩家之外的所有对象。我们不希望玩家出现在这个列表中,因为当我们检查玩家是否与block_list中的物体发生碰撞时,pygame 会继续,如果玩家出现在列表中,它会返回碰撞结果。

049 for i in range(50):

050     # This represents a block

051     block = Block(BLACK, 20, 15)

052

053     # Set a random location for the block

054     block.rect.x = random.randrange(screen_width)

055     block.rect.y = random.randrange(screen_height)

056

057     # Add the block to the list of objects

058     block_list.add(block)

059     all_sprites_list.add(block)

从第 49 行开始的循环向屏幕添加了 50 个黑色精灵块。第 51 行创建一个新块,设置颜色、宽度和高度。第 54 行和第 55 行设置了这个对象出现的坐标。第 58 行将方块添加到玩家可以碰撞的方块列表中。第 59 行将其添加到所有块的列表中。这应该与您在练习 13 中写的代码非常相似。

061 # Create a RED player block

062 player = Block(RED, 20, 15)

063 all_sprites_list.add(player)

第 61–63 行为我们的游戏设置了玩家。第 62 行创建了一个红色块,最终将作为播放器。该块被添加到第 63 行的all_sprites_list中,因此它可以被绘制,但不能被绘制到block_list中。

065 # Loop until the user clicks the close button.

066 done = False

067

068 # Used to manage how fast the screen updates

069 clock = pygame.time.Clock()

070

071 score = 0

072

073 # -------- Main Program Loop -----------

074 while not done:

075     for event in pygame.event.get():

076         if event.type == pygame.QUIT:

077             done = True

078

079     # Clear the screen

080     screen.fill(WHITE)

上面的代码是一个标准的程序循环,在第六章中首次介绍。第 71 行将我们的score变量初始化为 0。

082     # Get the current mouse position. This``returns

083     # as a list of two numbers.

084     pos = pygame.mouse.get_pos()

085

086     # Fetch the x and y out of the list,

087        # just like we’d fetch letters out of a string.

088     # Set the player object to the mouse location

089     player.rect.x = pos[0]

090     player.rect.y = pos[1]

第 84 行获取鼠标位置,类似于之前讨论的其他 pygame 程序。重要的新部分包含在第 89–90 行,其中包含精灵的矩形被移动到新的位置。请记住,这个 rect 是在第 31 行创建的,如果没有那一行,这段代码将无法运行。

092     # See if the player block has collided with anything.

093     blocks_hit_list = pygame.sprite.spritecollide(player, block_list, True)

这行代码获取由player引用的 sprite,并对照block_list中的所有 sprite 进行检查。代码返回一个重叠的精灵列表。如果没有重叠的精灵,它将返回一个空列表。布尔True将从列表中删除碰撞的精灵。如果设置为False,精灵将不会被移除。

095     # Check the list of collisions.

096     for block in blocks_hit_list:

097         score += 1

098         print(score)

这为在第 93 行创建的碰撞列表中的每个精灵循环。如果列表中有精灵,增加每次碰撞的分数。然后将分数打印到屏幕上。请注意,第 98 行的print不会将乐谱打印到主窗口和精灵窗口,而是打印到控制台窗口。弄清楚如何让分数显示在主窗口上是练习“收集精灵”的一部分

100     # Draw all the spites

101     all_sprites_list.draw(screen)

all_sprites_list所属的Group类有一个叫做draw的方法。该方法遍历列表中的每个 sprite,并调用该 sprite 的draw方法。这意味着只需一行代码,一个程序就可以让all_sprites_list中的每个精灵都被绘制出来。

103     # Go ahead and update the screen with what we’ve drawn.

104     pygame.display.flip()

105

106     # Limit to 60 frames per second

107     clock.tick(60)

108

109 pygame.quit()

当主循环完成时,第 103–109 行翻转屏幕并调用quit方法。

移动精灵

在到目前为止的例子中,只有玩家精灵移动。一个程序怎么能让所有的精灵都动起来?这很容易做到;只需要两步。

第一步是向Block类添加一个新方法。这种新方法叫做update。当对整个列表调用update时,将自动调用更新功能。

把这个放进雪碧里:

def update(self):

""" Called each frame. """

# Move block down one pixel

self.rect.y += 1

把这个放到主程序循环中:

# Call the update() method for all blocks in``t

block_list.update()

代码并不完美,因为块会从屏幕上掉下来,不再出现。这段代码将改进update函数,使块重新出现在顶部。

def update(self):

# Move the block down one pixel

self.rect.y += 1

if self.rect.y > screen_height:

self.rect.y = random.randrange(-100, -10)

self.rect.x = random.randrange(0, screen_width)

如果程序应该重置收集到屏幕顶部的块,可以使用以下代码更改 sprite:

def reset_pos(self):

""" Reset position to the top of the screen, at a random x location.

Called by update() or the main program loop if there is a collision.

"""

self.rect.y = random.randrange(-300, -20)

self.rect.x = random.randrange(0, screen_width)

def update(self):

""" Called each frame. """

# Move block down one pixel

self.rect.y += 1

# If block is too far down, reset to top of screen.

if self.rect.y > 410:

self.reset_pos()

当碰撞发生时,程序可能会调用reset_pos函数,而不是销毁块,块将移动到屏幕顶部准备收集。

# See if the player block has collided with anything.

blocks_hit_list = pygame.sprite.spritecollide(player, block_list, False)

# Check the list of collisions.

for block in blocks_hit_list:

score += 1

print(score)

# Reset block to the top of the screen to fall again.

block.reset_pos()

此示例的完整代码如下:

ProgramArcadeGames.com/python_examples/f.php?file=moving_sprites.py

如果你想看弹跳精灵的代码,请看这里:

ProgramArcadeGames.com/python_examples/f.php?file=moving_sprites_bounce.py

如果你想让它们转圈:

ProgramArcadeGames.com/python_examples/f.php?file=sprite_circle_movement.py

游戏课

回到第十章我们介绍了函数。在本章的最后,我们讨论了使用main函数的选项。随着程序变得越来越大,这种技术有助于我们避免因需要整理大量代码而带来的问题。我们的程序还没有那么大。然而,我知道有些人喜欢从一开始就组织好事情。

对于那些人来说,这里有另一种组织代码的可选技术。(如果您不在这个阵营中,您可以跳过这一部分,稍后当您的程序变得太大时再回来。)观看视频,了解该程序的工作原理。

ProgramArcadeGames.com/python_examples/f.php?file=game_class_example.py

其他示例

这里有几个你可以用精灵做什么的例子。其中一些还包括一个解释代码如何工作的链接视频。

拍摄东西

拍摄东西

A978-1-4842-1790-0_14_Figc_HTML.jpg

对射击游戏感兴趣吗?类似经典的太空入侵者?此示例显示了如何创建代表项目符号的精灵:

ProgramArcadeGames.com/python_examples/f.php?file=bullets.py

墙壁

你在寻找更多的冒险游戏吗?你不想让你的玩家到处乱逛吧?这显示了如何添加阻止玩家移动的墙:

ProgramArcadeGames.com/python_examples/f.php?file=move_with_walls_example.py

A978-1-4842-1790-0_14_Figd_HTML.jpg

跟着我们能碰到的墙走

等等?一个房间还不够冒险吗?你想让你的玩家在屏幕间移动吗?我们能做到!请看这个例子,玩家可能在一个多房间的迷宫中奔跑:

ProgramArcadeGames.com/python_examples/f.php?file=maze_runner.py

A978-1-4842-1790-0_14_Fige_HTML.jpg

多房间迷宫

平台

有兴趣打造一个平台化的,比如大金刚?我们需要使用与我们的墙壁示例相同的想法,但是增加一些重力:

ProgramArcadeGames.com/python_examples/f.php?file=platform_jumper.py

A978-1-4842-1790-0_14_Figf_HTML.jpg

在平台上跳跃

好的平台玩家可以左右移动。这是一个侧面滚动平台:

ProgramArcadeGames.com/python_examples/f.php?file=platform_scroller.py

A978-1-4842-1790-0_14_Figg_HTML.jpg

侧面滚动平台

甚至更酷的平台游戏也有移动的平台!看看这个例子是如何做到的:

ProgramArcadeGames.com/python_examples/f.php?file=platform_moving.py

A978-1-4842-1790-0_14_Figh_HTML.jpg

移动平台

蛇/蜈蚣

我偶尔会碰到一些读者想制作一个蛇或者蜈蚣类型的游戏。你有一条可以控制的多节蛇。这需要将每个段保存在一个列表中。虽然它需要学习两个新的命令,但这个游戏背后的概念并不难。

控制一条蛇或蜈蚣绕着屏幕走:

ProgramArcadeGames.com/python_examples/f.php?file=snake.py

A978-1-4842-1790-0_14_Figi_HTML.jpg

使用精灵工作表

这是一个广泛的例子,使用 sprite 表来提供平台游戏背后的图形。它支持多层次和移动平台。游戏被分成多个文件。ProgramArcadeGames.com/python_examples/en/sprite_sheets

A978-1-4842-1790-0_14_Figj_HTML.jpg

雪碧薄板平台车

回顾

多项选择测验

What is a Sprite? A graphic image that he computer can easily track, draw on the screen, and detect collisions with.   A very bright color that seems to glow.   A function that draws images to the screen.   A sprite is to Tinkerbell as a human is to Bob.     Which option best describes how a programmer use sprites in his or her program? Derive a new class from pygame.sprite.Sprite, and then create instances of those sprites and add them to sprite groups.   Create instances of pygame.sprite.Sprite and add them to sprite groups.   Use functions to draw images directly to the screen   Use bitmaps and blit images to the screen.     What is the standard way to draw sprites in a program? Add a sprite to a group. Then call .draw(screen) on the group.   Call the sprite’s .draw(screen) method.   Call the sprite’s .update(screen) method.   Call the sprite’s .blit(screen) method.     How does a program move a sprite pointed to by mysprite? Set new mysprite.rect.x and mysprite.rect.y values.   Set new mysprite.x and mysprite.y values.   Call mysprite.draw(x,y) with the desired x and y values.   Call mysprite.move(x,y) with the desired x and y values.     How does a sprite move itself? Create an update() method. Change self.rect.x and self.rect.y values.   Create an update() method. Change rect.x and rect.y values.   Create a move() method. Change self.x and self.y values.     If a programmer creates his/her own constructor for a sprite, what must be the first line of that constructor? super().__init__()   self.image = pygame.Surface([width, height])   self.image.set_colorkey(white)     If a programmer wants to create a transparent background for a sprite, what type of image should be avoided? jpg   png   gif     What does the True do in this line of code? sprites_hit_list = pygame.sprite.spritecollide(sprite, sprite_list, True) Removes sprite if any sprite in sprite_list is overlapping.   Creates an explosion effect when the sprites collide.   Creates a sound effect when the sprites collide.   Removes any sprite in sprite_list that is overlapping sprite.     What is special about a sprite’s update() function? It is called automatically each time through the game loop.   It is called automatically when the code calls update() on any list that sprite is in.   There is no special significance to that function.     What is the proper command to add a sprite to an instance of pygame.sprite.Group() pointed to by a sprite_list? sprite_list.append(my_sprite)   sprite_list.add(my_sprite)   sprite_list.insert(my_sprite)     If the screen is 600 wide and 400 tall, where will this sprite be moved? mysprite.rect.x = 600 mysprite.rect.y = 400 A978-1-4842-1790-0_14_Figk_HTML.jpg   A978-1-4842-1790-0_14_Figl_HTML.jpg   A978-1-4842-1790-0_14_Figm_HTML.jpg   A978-1-4842-1790-0_14_Fign_HTML.jpg    

锻炼

查看附录中本章附带的练习“收集精灵”。

十五、库和模块

A978-1-4842-1790-0_15_Figa_HTML.jpg

库是函数和类的代码集合。通常,这些库是由其他人编写并引入项目中的,这样程序员就不必重新发明轮子。在 Python 中,用来描述代码库的术语是模块。

通过使用import pygameimport random,目前创建的程序已经使用了模块。一个库可以由多个可以导入的模块组成。通常一个库只有一个模块,所以这些词有时可以互换使用。

模块通常被组织成具有相似功能的组。在这个类中,程序已经使用了来自math模块、random模块和pygame库的函数。可以对模块进行组织,以便单个模块包含其他模块。例如,pygame模块包含pygame.drawpygame.imagepygame.mouse的子模块。

除非程序要求,否则模块不会被加载。这样可以节省时间和计算机内存。本章介绍了如何创建模块以及如何导入和使用该模块。

为什么要创建库?

程序员创建自己的库有三个主要原因:

It breaks the code into smaller, easier to use parts.   It allows multiple people to work on a program at the same time.   The code written can be easily shared with other programmers.  

本书中已经创建的一些程序已经开始变得相当长。通过将一个大程序分成几个小程序,管理代码就更容易了。例如,在前一章的 sprite 示例中,程序员可以将 sprite 类移动到一个单独的文件中。在一个复杂的程序中,每个精灵可能包含在它自己的文件中。

如果多个程序员在同一个项目上工作,如果所有的代码都在一个文件中,这几乎是不可能的。然而,通过将程序分成多个部分,它变得更容易。一个程序员可以开发一个Orc sprite 类。另一个程序员可以处理Goblin sprite 类。由于精灵在不同的文件中,程序员不会遇到冲突。

现代程序员很少从头开始构建程序。通常,程序是由共享相同功能的其他程序的部分构建而成的。如果一个程序员编写了可以处理抵押申请表格的代码,那么这些代码最好是放在一个库中。然后,需要管理该银行的抵押申请表单的任何其他程序都可以调用该库。

创建您自己的模块/库文件:

在这个例子中,我们将把一个短程序分成多个文件。这里我们在一个名为test.py的文件中有一个函数以及对该函数的调用:

# Foo function

def foo():

print("foo!")

# Foo call

foo()

是的,这个程序在一个文件中不会太长。但是如果函数和主程序代码都很长,那就不一样了。如果我们有几个函数,每个函数都有 100 行长,那么管理这么大的文件将会非常耗时。但是在这个例子中,为了清楚起见,我们将保持代码简短。

我们可以将foo函数移出这个文件。那么这个文件将只剩下主程序代码。(在这个例子中,除了学习如何做之外,没有理由将它们分开。)

为此,创建一个新文件并将foo函数复制到其中。用名称my_functions.py保存新文件。文件必须保存在与test.py相同的目录下。

# Foo function

def foo():

print("foo!")

# Foo call that doesn’t work

foo()

不幸的是,事情没有这么简单。文件test.py不知道去查看my_functions.py文件并导入它。我们必须添加命令来导入它:

# Import the my_functions.py file

import my_functions

# Foo call that still doesn’t work

foo()

那还是不行。我们遗漏了什么?就像我们导入 pygame 的时候,要把包名放在函数前面一样。像这样:

# Import the my_functions.py file

import my_functions

# Foo call that does work

my_functions.foo()

这是因为my_functions.被加到了函数调用的前面。

命名空间

一个程序可能需要使用两个库文件。如果这些库有同名的函数会怎么样?如果有两个名为print_report的函数——一个打印成绩,一个打印账户报表,会怎么样?例如:

def print_report():

print("Student Grade Report:" )

def print_report():

print("Financial Report:" )

如何让程序指定调用哪个函数?嗯,那很简单。您可以指定名称空间。命名空间是出现在下面代码中的函数名之前的作品:

import student_functions

import financial_functions

student_functions.print_report()

financial_functions.print_report()

因此,现在我们可以看到为什么这可能是必要的。但是如果没有名字冲突呢?每次都键入一个名称空间可能会令人厌烦。您可以通过将库导入本地名称空间来解决这个问题。本地命名空间是一个函数、变量和类的列表,您不必在前面加上命名空间。回到foo的例子,让我们删除原来的导入并用一种新的导入类型替换它:

# import foo

from my_functions import *

foo()

即使没有在函数调用前添加my_functions.,这也是可行的。星号是一个通配符,将从my_functions导入所有函数。如果需要,程序员可以通过指定函数名来导入单独的函数。

第三方库

使用 Python 时,可以使用 Python 内置的许多库。看看这里提供的所有库:

http://docs.python.org/3/py-modindex.html

可以下载并安装其他库。有处理网络、复数、数据库等的库。

  • Pygame:用于创建游戏的库。http://www.pygame.org/docs/
  • wxPython:创建 GUI 程序,包括窗口、菜单等等。http://www.wxpython.org/
  • pydot:生成复杂的有向和无向图。http://code.google.com/p/pydot/
  • NumPy:用于处理矩阵的复杂库。http://numpy.scipy.org/

Python 库的精彩列表和安装程序的链接可以在这里找到:http://www.lfd.uci.edu/~gohlke/pythonlibs/

浏览可用的库列表可以帮助您集思广益,找出可以创建的程序类型。大多数编程都涉及到组装大型部件,而不是从头开始编写所有东西。

回顾

多项选择测验

What is a library? A collection of functions and/or classes that can be imported into a project.   A store where you can buy code from other developers.   Any code that has not been written by a developer.   A .pyc file.     Why would a person create a library? It provides an easy way for developers to share code between projects and other developers.   It makes the code run faster.   It makes the code smaller.   Libraries are something to be avoided by developers.     Why does the following code not work? The first file: def foo():     print("foo!") And the second file: import my_functions foo() The program should read my_functions.foo()   The program should read import my_functions.py   The program should read import foo   The program should read from foo import my_functions     What is the proper code to import the math library into the local namespace? from math import *   import math   import local   from math import local   import math into local     What does the asterisk represent in the following line of code: from my_functions import * Wildcard. Import every function in the my_functions module.   It represents the local namespace.   My God, it’s full of stars!    

简答工作表

What is a Python library?   What are some of the reasons why a programmer would want to create his/her own library file?   There are two ways to import library files in Python. Give an example of each.   How do calls to functions and classes differ depending on how the library is imported?   Can library files import other library files?   What is a namespace?  

锻炼

查看附录中本章附带的练习“移动精灵”。

十六、搜索

A978-1-4842-1790-0_16_Figa_HTML.jpg

搜索是计算机一直在做的一项重要且非常常见的操作。每当有人按 ctrl-f 键进行“查找”时,当用户使用“键入”键快速选择一个项目时,或者当 web 服务器提取有关客户的信息以显示带有客户订单的定制网页时,都会使用搜索。

有很多方法可以搜索数据。谷歌将一整个价值数十亿美元的公司建立在这个事实上。本章介绍两种最简单的搜索方法:线性搜索和二分搜索法。

从文件中读取

在讨论如何搜索之前,我们需要学习如何从文件中读取数据。从文件中读入数据集比每次手工输入要有趣得多。

假设我们需要创建一个程序,让我们能够快速找到一个超级恶棍的名字。首先,我们的程序需要一个超级反派的数据库。要下载该数据集,请下载并保存该文件:

http://ProgramArcadeGames.com/chapters/16_searching/super_villains.txt

这些是 nine.frenchboys.net 网站随机生成的名字。保存这个文件,并记住你把它保存到哪个目录。

在与super_villains.txt相同的目录下,创建、保存并运行以下 python 程序:

file = open("super_villains.txt")

for line in file:

print(line)

这段代码中只有一个新命令open。因为是类似print的内置函数,所以不需要import。关于这个函数的全部细节可以在 https://docs.python.org/3/library/functions.html#open 中找到,但是在这一点上,这个命令的文档太专业了,甚至不值得一看。

上面的程序有两个问题,但是它提供了一个简单的读取文件的例子。第 1 行打开一个文件,并准备好读取它。文件的名称在引号之间。新变量file是一个对象,表示正在读取的文件。第 3 行展示了如何使用普通的for循环来逐行读取文件。把file想象成一个行列表,当程序在循环中运行时,新的变量line将被设置到每一行。

尝试运行该程序。它的一个问题是文本以双倍行距打印。原因是从文件中取出并存储在变量line中的每一行都包含回车作为字符串的一部分。还记得第一章中介绍的回车和换行吗?print语句添加了另一个回车,结果是双倍行距的输出。

第二个问题是文件打开了,但没有关闭。这个问题不像双倍行距问题那样明显,但是很重要。Windows 操作系统一次只能打开这么多文件。一个文件通常一次只能由一个程序打开。让文件保持打开状态会限制其他程序对该文件的操作,并会占用系统资源。有必要关闭该文件,让 Windows 知道该程序不再使用该文件。在这种情况下,这并不太重要,因为一旦任何程序完成运行,Windows 将自动关闭任何打开的文件。但是因为这样编程是一个坏习惯,所以让我们更新代码:

file = open("super_villains.txt")

for line in file:

line = line.strip()

print(line)

file.close()

上面的清单效果更好。它有两个新的补充。第 4 行是对每个String类中内置的strip方法的调用。这个函数返回一个新字符串,没有原始字符串的尾随空格和回车。方法不会改变原始字符串,而是创建一个新字符串。这行代码不起作用:

line.strip()

如果程序员希望原始变量引用新字符串,她必须将它赋给新返回的字符串,如第 4 行所示。

第二次添加在第 7 行。这将关闭文件,这样操作系统就不必在程序结束后再去清理打开的文件。

读入数组

将文件的内容读入一个数组是很有用的,这样程序就可以在以后对它进行处理。这可以在 python 中用以下代码轻松完成:

# Read in a file from disk and put it in an array.

file = open("super_villains.txt")

name_list = []

for line in file:

line = line.strip()

name_list.append(line)

file.close()

这结合了如何读取文件的新模式,以及先前学习的如何创建空数组并在新数据进入时追加到该数组的模式,这在第八章的中有所介绍。为了验证文件是否被正确读入数组,程序员可以打印数组的长度:

print( "There were",len(name_list),"names in the file.")

或者程序员可以带来数组的全部内容:

for name in name_list:

print(name)

继续进行不同的搜索之前,请确保您可以读取该文件。

线性搜索

如果一个程序在一个数组中有一组数据,它如何找到一个特定的元素呢?有两种方法可以做到这一点。第一种方法是使用线性搜索。这从第一个元素开始,并不断比较元素,直到找到所需的元素(或用尽元素。)

线性搜索算法

# --- Linear search

key = "Morgiana the Shrew"

i = 0

while i < len(name_list) and name_list[i] != key:

i += 1

if i < len(name_list):

print( "The name is at position", i)

else:

print( "The name was not in the list." )

线性搜索相当简单。第 2 行设置了一个增量变量,该变量将准确跟踪程序下一次需要检查的列表位置。需要检查的第一个元素是零,所以i设置为零。

下一行有点复杂。计算机需要保持循环,直到两件事情之一发生。它找到了元素,或者用完了元素。第一个比较看我们正在检查的当前元素是否小于列表的长度。如果是这样,我们可以继续循环。第二个比较查看名称列表中的当前元素是否等于我们正在搜索的名称。

这种查看程序是否已经用完元素的检查必须首先发生。否则,程序将检查不存在的元素,这将导致错误。

如果在第 3 行中满足继续搜索的条件,第 4 行只是移动到下一个元素。

在循环结束时,程序检查第 6 行是否到达了列表的末尾。记住,n 个元素的列表编号为 0 到 n-1。因此,如果i等于列表的长度,则已经到达末尾。如果少了,我们就找到了元素。

线性搜索的变体

线性搜索的变体可用于创建几种常见的算法。例如,假设我们有一份外星人的名单。我们可能要检查这群外星人,看看是否有一个外星人是绿色的。还是所有的外星人都是绿色的?哪些外星人是绿色的?

首先,我们需要定义我们的外星人:

class Alien:

""" Class that defines an alien"""

def __init__(self, color, weight):

""" Constructor. Set name and color"""

self.color = color

self.weight = weight

然后,我们需要创建一个函数来检查并查看它是否具有我们正在寻找的属性。在这种情况下,是绿色的吗?我们将假设颜色是一个文本字符串,我们将把它转换成大写以消除大小写的敏感性。

def has_property(my_alien):

""" Check to see if an item has a property.

In this case, is the alien green? """

if my_alien.color.upper() == "GREEN":

return True

else:

return False

至少有一个项目有属性吗?

至少有一个外星人是绿色的吗?我们可以查一下。这项检查背后的基本算法是:

def check_if_one_item_has_property_v1(my_list):

""" Return true if at least one item has a

property. """

i = 0

while i < len(my_list) and not has_property(my_list[i]):

i += 1

if i < len(my_list):

# Found an item with the property

return True

else:

# There is no item with the property

return False

这也可以用一个for循环来完成。在这种情况下,一旦找到项目,循环将通过使用return提前退出。代码更短,但不是每个程序员都喜欢它。一些程序员认为循环不应该过早地以returnbreak语句结束。这完全取决于个人偏好——或者买单人的个人偏好。

def check_if_one_item_has_property_v2(my_list):

""" Return true if at least one item has a

property. Works the same as v1, but less code. """

for item in my_list:

if has_property(item):

return True

return False

所有项目都有属性吗?

外星人都是绿色的吗?这段代码与前面的例子非常相似。找出不同之处,看看你是否能找出变化背后的原因。

def check_if_all_items_have_property(my_list):

""" Return true if at ALL items have a property. """

for item in my_list:

if not has_property(item):

return False

return True

创建包含所有匹配属性的项目的列表

如果你想要一份绿色外星人的名单呢?这是我们之前的代码和将项目添加到列表中的代码的组合,我们在第七章的中学到了这些。

def get_matching_items(list):

""" Build a brand new list that holds all the items

that match our property. """

matching_list = []

for item in list:

if has_property(item):

matching_list.append(item)

return matching_list

你如何在测试中运行所有这些?上面的代码可以与下面的代码结合起来运行:

alien_list = []

alien_list.append(Alien("Green", 42))

alien_list.append(Alien("Red", 40))

alien_list.append(Alien("Blue", 41))

alien_list.append(Alien("Purple", 40))

result = check_if_one_item_has_property_v1(alien_list)

print("Result of test check_if_one_item_has_property_v1:", result)

result = check_if_one_item_has_property_v2(alien_list)

print("Result of test check_if_one_item_has_property_v2:", result)

result = check_if_all_items_have_property(alien_list)

print("Result of test check_if_all_items_have_property:", result)

result = get_matching_items(alien_list)

print("Number of items returned from test get_matching_items:", len(result))

有关完整的工作示例,请参见:

programarcadegames.com/python_examples/show_file.php?file=property_check_examples.py

这些常见的算法可以作为一个更大问题的解决方案的一部分,比如查找客户列表中所有无效的地址。

二进位检索

使用二分搜索法可以更快地搜索列表。二分搜索法的过程可以用经典的数字猜谜游戏“猜一个 1 到 100 之间的数字”来描述。为了更容易理解这个过程,让我们将这个游戏修改为“猜一个 1 到 128 之间的数字”数字范围包括 1 和 128,这意味着 1 和 128 都是可能的。

如果一个人使用线性搜索作为猜测秘密数字的方法,这个游戏将会相当漫长和无聊。

Guess a number 1 to 128: 1

Too low.

Guess a number 1 to 128: 2

Too low.

Guess a number 1 to 128: 3

Too low.

....

Guess a number 1 to 128: 93

Too low.

Guess a number 1 to 128: 94

Correct!

大多数人会用二分搜索法来查找号码。下面是一个用二分搜索法玩游戏的例子:

Guess a number 1 to 128: 64

Too low.

Guess a number 1 to 128: 96

Too high.

Guess a number 1 to 128: 80

Too low.

Guess a number 1 to 128: 88

Too low.

Guess a number 1 to 128: 92

Too low.

Guess a number 1 to 128: 94

Correct!

在每一轮数字猜谜游戏中,猜谜者都能够通过猜高或猜低来消除一半的问题空间。

在二分搜索法中,需要跟踪答案可能在列表中的上界和下界。计算机或猜数字的人选择这些元素的中点。重温示例:

下限为 1,上限为 128,中点为 \frac{1+128}{2}=64.5

Guess a number 1 to 128: 64

Too low.

下限 65,上限 128,中点 \frac{65+128}{2}=96.5

Guess a number 1 to 128: 96

Too high.

下限为 65,上限为 95,中点为 \frac{65+95}{2}=80

Guess a number 1 to 128: 80

Too low.

下限为 81,上限为 95,中点为 \frac{81+95}{2}=88

Guess a number 1 to 128: 88

Too low.

下限为 89,上限为 95,中点为 \frac{89+95}{2}=92

Guess a number 1 to 128: 92

Too low.

下限为 93,上限为 95,中点为 \frac{93+95}{2}=94

Guess a number 1 to 128: 94

Correct!

二分搜索法需要更少的猜测。最坏的情况是,它可以在 7 次猜测中猜出 1 到 128 之间的一个数字。再猜一次就把限制提高到 256。九次猜测可以得到一个 1 到 512 之间的数字。一个人只要猜 32 次,就能得到 1 到 42 亿之间的数字。

要计算给定一定次数的猜测,列表可以有多大,公式如下:n=x g 其中 n 是列表的大小,g 是猜测的次数。例如:

27

28

29

232

如果你知道问题的大小,我们可以使用 log 函数计算出猜测的次数。具体来说,就是以 2 为基数的对数。如果不指定底数,大多数人会假设你指的是底数为 e ≈ 2.71828 的自然对数,这不是我们想要的。例如,使用以 2 为底的对数来计算猜测次数:

log2

log2

数学够了!代码在哪里?执行二分搜索法的代码比线性搜索更复杂:

# --- Binary search

key = "Morgiana the Shrew";

lower_bound = 0

upper_bound = len(name_list)-1

found = False

# Loop until we find the item, or our upper/lower bounds meet

while lower_bound <= upper_bound and not found:

# Find the middle position

middle_pos = (lower_bound + upper_bound) // 2

# Figure out if we:

# move up the lower bound, or

# move down the upper bound, or

# we found what we are looking for

if name_list[middle_pos] < key:

lower_bound = middle_pos + 1

elif name_list[middle_pos] > key:

upper_bound = middle_pos - 1

else:

found = True

if found:

print( "The name is at position", middle_pos)

else:

print( "The name was not in the list." )

因为列表从元素 0 开始,所以第 3 行将下限设置为零。第 4 行将上限设置为列表长度减 1。因此,对于 100 个元素的列表,下限将是 0,上限是 99。

第 5 行的布尔变量将用于让 while 循环知道已经找到了元素。

第 6 行检查是否找到了元素,或者我们是否用完了元素。如果我们用完了所有的元素,下界将会等于上界。

第 7 行找到中间位置。有可能得到 64.5 左右的中间位置。不可能查找位置 64.5。(尽管 J. K .罗琳相当聪明地提出了平台 9\raisebox{1ex}{$3$}\!\left/ \!\raisebox{-1ex}{$4$}\right. ,但这在这里行不通。最好的处理方式是使用第六章中首次引入的//操作符。这类似于/操作符,但只会返回整数结果。例如,11 // 2会给出 5 作为答案,而不是 5.5。

从第 8 行开始,程序检查猜测值是高、低还是正确。如果猜测值较低,则下限向上移动,刚好超过猜测值。如果猜测值太高,上限就移到猜测值之下。如果已经找到答案,则将found设置为True以结束搜索。

对于一个包含 100 个元素的列表,人们可以合理地猜测,平均来说,使用线性搜索,程序在找到元素之前必须检查其中的 50 个。对于二分搜索法,平均来说你仍然需要猜 7 次。在高级算法课程中,你可以找到精确的公式。在本课程中,假设平均情况和最坏情况相同。

回顾

多项选择测验

Before reading from a file, a program must: Open it with the open command.   Initialize it with the init command.   Reset the file with a reset command.     In order to read in each line of the file a program should: Use a for loop   Use the read_all_lines command.   Access each line in an array.     What will happen if a program fails to close a file after reading it? The file will be marked as busy and will be inaccessible until the program ends.   The file will not be able to be used until the computer restarts.   The programmer will get a call from his mother reminding him that he forgot to close his that file, again.   Nothing, it doesn’t really matter if you don’t close the file.     What processing usually needs to be done on a line after it has been read in? The carriage return and/or line feed need to be stripped of the end of the line.   The line needs to be converted to uppercase.   The line needs to be converted from a string of integers to a string of letters.   The dilithium crystals must be recalibrated before use.     What is wrong with this linear search? i = 0 while my_list[i] != key and i < len(my_list): i += 1 The loop needs to check to see if we ran out of list items before checking to see if the item is equal to the key.   The first check should be == not !=.   The second check should be <= not <.   The second check should be > not <.     After this code runs, what is the proper way to tell if the item was found or not? i = 0 while i < len(my_list) and my_list[i] != key: i += 1 if i == len(my_list):   if my_list[i] != key:   if my_list[i] == key:   if i > len(my_list):     A binary search starts looking: In the middle of the list.   At the beginning of the list.   At the end of the list.   At a random spot in the list.     Using a binary search, a list of 128 elements takes at most how many times through the search loop? 7   64   128   1     In a binary search, how do you know if an element is not in the list and the search should stop? The lower bound is equal or greater than the upper bound.   After every item has been checked.   When the key is not equal to the middle element.   When i is greater than or equal to the list length.     If the key is less than the middle element, the search should: Move the upper bound down to the middle element.   Move the lower bound up to the middle element.    

简答工作表线性搜索审核

假设一个程序使用线性搜索,回答下列问题:

If a list has n elements, in the best case how many elements would the computer need to check before it found the desired element?   If a list has n elements, in the worst case how many elements would the computer need to check before it found the desired element? (Remember, give your answer in terms of n.)   If a list has n elements, how many elements need to be checked to determine that the desired element does not exist in the list?   If a list has n elements, what would the average number of elements be that the computer would need to check before it found the desired element?   Take the example linear search code and put it in a function called linear_search. Take in the list along with the desired element as parameters. Return the position of the element, or -1 if it was not found. Once you’ve written the function, try it out with the following code to see if it works: # --- Put your definition for linear_search right below: # --- Now if the function works, all these tests should pass: my_list = [4, 3, 2, 1, 5, 7, 6] r = linear_search(my_list, 3) if r == 1:     print("Test A passed") else:     print("Test A failed") r = linear_search(my_list, 2) if r == 2:     print("Test B passed") else:     print("Test B failed") r = linear_search(my_list, 10) if r == -1:     print("Test C passed") else:     print("Test C failed")  

二分搜索法评论

假设一个程序使用二分搜索法,并且搜索列表是有序的,请回答以下问题:

If a list has n elements, in the best case how many elements would the computer need to check before it found the desired element?   If a list has n elements, in the worst case how many elements would the computer need to check before it found the desired element?   If a list has n elements, how many elements need to be checked to determine that the desired element does not exist in the list?   If a list has n elements, what would the average number of elements be that the computer would need to check before it found the desired element?   Take the example binary search code and put it in a function named binary_search. Take in the list along with the desired element as parameters. Return the position of the element, or -1 if it was not found. Once you’ve written the function, try it out with the following code to see if it works: # --- Put your definition for binary_search right below: # --- Now if the function works, all these tests should pass: my_list = [0, 3, 5, 12, 18, 50, 70, 78] r = binary_search(my_list, 3) if r == 1:     print("Test A passed") else:     print("Test A failed") r = binary_search(my_list, 2) if r == 2:     print("Test B passed") else:     print("Test B failed") r = binary_search(my_list, 10) if r == -1:     print("Test C passed") else:     print("Test C failed")  

挑战问题

Does the following function correctly detect whether a list contains at least one positive element? Write code to try it out. Explain why it does work or why it does not work. Come up with working code. def detect_positive(list):     for element in list:         if element > 0:             return True         else:             return False  

锻炼

查看附录中本章附带的“拼写检查”练习。

十七、数组支持的网格

像扫雷、井字游戏和许多类型的冒险游戏都将游戏数据保存在数字网格中。例如,井字游戏棋盘:

|   | O | O | |   | X |   | | X |   |   |

…可以使用数字网格来表示空白点、O 和 X,如下所示:

| Zero | Two | Two | | Zero | one | Zero | | one | Zero | Zero |

这个数字网格也可以称为二维数组或矩阵。(最后,我们开始学习矩阵。)表格中的数字值代表每个电路板位置应显示的内容。在前面的例子中,0 代表一个没有人打球的位置,1 代表一个 X,2 代表一个 o。

A978-1-4842-1790-0_17_Figa_HTML.jpg

扫雷游戏,显示支持网格的数字

上图是经典扫雷游戏中的一个例子。这个例子被修改为在左边显示经典显示,在右边显示棋盘上的数字网格。

数字10代表一个地雷,数字0代表一个没有被点击的空间,数字9代表一个被清除的空间。数字 1 到 8 代表周围 8 个方块内有多少个地雷,只有当用户点击方块时才会填充。

扫雷实际上可以有两个网格:一个用于常规显示,一个完全独立的数字网格将跟踪用户是否在板上放置了标记地雷位置的旗帜。

经典冒险游戏地图是使用平铺地图编辑器创建的。这些是巨大的网格,每个位置只是一个代表那里地形类型的数字。地形可以是泥土、道路、小径、绿草、棕草等等。下图所示的类似于 Tiled Qt 的程序允许开发人员轻松地制作这些地图并将网格写入磁盘。

A978-1-4842-1790-0_17_Figb_HTML.jpg

使用 Qt 图块创建冒险地图

冒险游戏也使用多个数字网格,就像扫雷有一个地雷网格和一个单独的旗帜网格。冒险游戏中的一个格子或层代表你可以行走的地形;另一个是你不能在上面行走的东西,比如墙和树;一层是可以瞬间杀死你的东西,比如熔岩或无底洞;一个是可以拿起来到处移动的物体;还有一层是用来放置怪物的。

像这样的地图可以加载到 Python 程序中,但不幸的是,如何管理的完整描述超出了本书的范围。像 https://github.com/bitcraft/PyTMX 这样的项目提供了加载这些地图所需的一些代码。

应用

说够了,我们来写点代码吧。这个例子将创建一个网格,如果我们显示一个白色或绿色块,这个网格将被触发。我们可以通过点击来改变网格值并使其变成绿色。这是像扫雷,战舰,连接四等基于网格的游戏的第一步。

转到示例代码页,下载基本模板文件:ProgramArcadeGames.com/python_examples/f.php?file=pygame_base_template.py

从空白模板文件开始,尝试按照这里的说明重新创建这个程序。最后的程序在这一章的末尾,但是不要跳到前面去复制它!如果你那样做,你将什么也学不到。任何人都可以复制和粘贴代码,但如果你能重新创建这个程序,你就拥有了人们愿意为之付费的技能。如果你只会复制粘贴,那你就在这里浪费时间了。

绘制网格

Adjust the program’s window size to 255×255 pixels.   Create variables named width, height, and margin. Set the width and height to 20. This will represent how large each grid location is. Set the margin to 5. This represents the margin between each grid location and the edges of the screen. Create these variables before the main program loop.   Draw a white box in the upper left corner. Draw the box drawn using the height and width variables created earlier. (Feel free to adjust the colors.) When you get done your program’s window should look like the next figure.

A978-1-4842-1790-0_17_Figc_HTML.jpg

Step 3   Use a for loop to draw 10 boxes in a row. Use column for the variable name in the for loop. The output will look like one long box until we add in the margin between boxes. See the next figure.

A978-1-4842-1790-0_17_Figd_HTML.jpg

Step 4   Adjust the drawing of the rectangle to add in the margin variable. Now there should be gaps between the rectangles. See the next figure.

A978-1-4842-1790-0_17_Fige_HTML.jpg

Step 5   Add the margin before drawing the rectangles, in addition to between each rectangle. This should keep the box from appearing right next to the window edge. See the next figure.

A978-1-4842-1790-0_17_Figf_HTML.jpg

Step 6   Add another for loop that also will loop for each row. Call the variable in this for loop row. Now we should have a full grid of boxes. See the next figure.

A978-1-4842-1790-0_17_Figg_HTML.jpg

Step 7  

普及网格

现在我们需要创建一个二维数组。不幸的是,在 Python 中创建二维数组不像在其他一些计算机语言中那么容易。有一些可以为 Python 下载的库使它变得容易,但是对于这个例子,它们将不会被使用。

To create a two-dimensional array and set an example, use the code below: # --- Create grid of numbers # Create an empty list grid = [] # Loop for each row for row in range(10):     # For each row, create a list that will     # represent an entire row     grid.append([])     # Loop for each column     for column in range(10):         # Add a the number zero to the current row         grid[row].append(0) A much shorter example is below, but this example uses some odd parts of Python that I don’t bother to explain in this book: grid = [[0 for x in range(10)] for y in range(10)] Use one of these two examples and place the code to create our array ahead of your main program loop.   Set an example location in the array to 1. Two-dimensional arrays are usually represented addressed by first their row and then the column. This is called a row-major storage. Most languages use row-major storage, with the exception of Fortran and MATLAB. Fortran and MATLAB use column-major storage. # Set row 1, column 5 to one grid[1][5] = 1 Place this code somewhere ahead of your main program loop.   Select the color of the rectangle based on the value of a variable named color. Do this by first finding the line of code where the rectangle is drawn. Ahead of it, create a variable named color and set it equal to white. Then replace the white color in the rectangle declaration with the colorvariable.   Select the color based on the value in the grid. After setting color to white, place an if statement that looks at the value in grid[row][column] and changes the color to green if the grid value is equal to 1. There should now be one green square. See the following figure.

A978-1-4842-1790-0_17_Figh_HTML.jpg

Step 11   Print “click” to the screen if the user clicks the mouse button. See bitmapped_graphics.py for example code of how to detect a mouse click.   Print the mouse coordinates when the user clicks the mouse. See move_mouse.py for an example on getting the position of the mouse. See the next figure.

A978-1-4842-1790-0_17_Figi_HTML.jpg

Step 13   Convert the mouse coordinates into grid coordinates. Print those instead. Remember to use the width and height of each grid location combined with the margin. It will be necessary to convert the final value to an integer. This can be done by using int or by using the integer division operator // instead of the normal division operator /. See the next figure.

A978-1-4842-1790-0_17_Figj_HTML.jpg

Step 14   Set the grid location at the row/column clicked to 1. See the next figure.

A978-1-4842-1790-0_17_Figk_HTML.jpg

Step 15  

最终计划

"""

Example program to show using an array to back a grid on-``s

Sample Python/Pygame Programs

http://programarcadegames.com/

Explanation video:http://youtu.be/mdTeqiWyFnc

"""

import pygame

# Define some colors

BLACK = (0, 0, 0)

WHITE = (255, 255, 255)

GREEN = (0, 255, 0)

RED = (255, 0, 0)

# This sets the WIDTH and HEIGHT of each grid location

WIDTH = 20

HEIGHT = 20

# This sets the margin between each cell

MARGIN = 5

# Create a 2 dimensional array. A two dimensional

# array is simply a list of lists.

grid = []

for row in range(10):

# Add an empty array that will hold each cell

# in this row

grid.append([])

for column in range(10):

grid[row].append(0)  # Append a cell

# Set row 1, cell 5 to one. (Remember rows and

# column numbers start at zero.)

grid[1][5] = 1

# Initialize pygame

pygame.init()

# Set the HEIGHT and WIDTH of the screen

WINDOW_SIZE = [255, 255]

screen = pygame.display.set_mode(WINDOW_SIZE)

# Set title of screen

pygame.display.set_caption("Array Backed Grid")

# Loop until the user clicks the close button.

done = False

# Used to manage how fast the screen``upd

clock = pygame.time.Clock()

# -------- Main Program Loop -----------

while not done:

for event in pygame.event.get():  # User did something

if event.type == pygame.QUIT:  # If user clicked close

done = True  # Flag that we are done so we exit this loop

elif event.type == pygame.MOUSEBUTTONDOWN:

# User clicks the mouse. Get the position

pos = pygame.mouse.get_pos()

# Change the x/y screen coordinates to grid coordinates

column = pos[0] // (WIDTH + MARGIN)

row = pos[1] // (HEIGHT + MARGIN)

# Set that location to zero

grid[row][column] = 1

print("Click ", pos, "Grid coordinates: ", row, column)

# Set the screen background

screen.fill(BLACK)

# Draw the grid

for row in range(10):

for column in range(10):

color = WHITE

if grid[row][column] == 1:

color = GREEN

pygame.draw.rect(screen,

color,

[(MARGIN + WIDTH) * column + MARGIN,

(MARGIN + HEIGHT) * row + MARGIN,

WIDTH,

HEIGHT])

# Limit to 60 frames per``secon

clock.tick(60)

# Go ahead and update the screen with what we’ve drawn.

pygame.display.flip()

# Be IDLE friendly. If you forget this line, the program will ’hang’

# on exit.

pygame.quit()

开始做你自己的视频游戏吧!

回顾

多项选择测验

In computer science, a grid of numbers is called a: Two-dimensional array   Bingo board   One-dimensional array   Two-dimensional board     To print the value of the top left corner of a 10x10 two-dimensional array, the current code would be: print(my_array[0][0])   print(my_array[1][1])   print(my_array[0,0])   print(my_array[1,1])     To store a 10 into an x, y position on the grid of (0, 5), what is the correct code? my_array[5][0] = 10   my_array[0][5] = 10   [0][5] = 10   my_array = 10   my_array[10] = (0,5)   print( my_arrayp[1,1] )     To process an entire two-dimensional array, a program needs: Two nested for loops: one for each row, one for each element in the row.   Two sequential for loops: one for each row, one for each element in the row.   One for loop to process every element.   A function for each element in the grid.   Two nested classes: one for each row, one for each element.     In the chapter example, how does the program find which grid location was clicked on with the mouse? Divide coordinates by the size of each grid location (including the margin).   Subtract the margin, divide by grid size.   Subtract the grid size.   Divide the grid size by the x and y coordinates.    

简答工作表

Start with the final program. Modify it so that rather than just changing the block the user clicks on, it also changes the blocks of the squares next to the user’s click. If the user clicks on an edge, make sure the program doesn’t crash and still handles the click appropriately.   Write a celebrity-finding function. Start with a function check_celebrity that takes an n by n matrix named grid as a parameter. The grid location grid[i][j] = 1 if person i knows person j and grid[i][j] = 0 otherwise. (Assume that grid[i][i] = 1 for every i, since every person knows him/herself.) A celebrity is a person who is known by everyone and does not know anyone besides him/herself. Write a function that given the matrix grid, prints all the celebrities. For example, in the following grid person 2 is a celebrity:      0  1  2  3   -------------- 0 |  1  1  1  0 1 |  0  1  1  0 2 |  0  0  1  0 3 |  1  0  1  1 In the next example, no one is a celebrity:      0  1  2  3  4   ---------------- 0 |  1  1  1  0  1 1 |  0  1  1  0  1 2 |  0  0  1  0  0 3 |  1  0  1  1  1 4 |  1  0  0  1  1 Remember: A matrix can be represented as a list of lists, where each sublist is a row of the matrix. For example, the first matrix can be represented as: grid = [ [1, 1, 1, 0], [0, 1, 1, 0], [0, 0, 1, 0], [1, 0, 1, 1] ] Or you can use multiple lines to define the grid: grid = [ [1, 1, 1, 0],          [0, 1, 1, 0],          [0, 0, 1, 0],          [1, 0, 1, 1] ] You can test your function with code like the following test cases: print("Test 1, Should show #2 is a celebrity.") grid = [ [1, 1, 1, 0],          [0, 1, 1, 0],          [0, 0, 1, 0],          [1, 0, 1, 1] ] check_celebrity(grid) print("Test 2, Should show no one is a celebrity.") grid = [ [1, 1, 1, 0, 1],          [0, 1, 1, 0, 1],          [0, 0, 1, 0, 0],          [1, 0, 0, 1, 1],          [1, 0, 0, 1, 1] ] check_celebrity(grid) print("Test 3, Should show #2 is a celebrity.") grid = [ [1, 1, 1, 0, 1],          [0, 1, 1, 0, 1],          [0, 0, 1, 0, 0],          [0, 0, 1, 0, 1],          [1, 0, 1, 1, 1] ] check_celebrity(grid) print("Test 4, Should show no one is a celebrity.") grid = [ [1, 1, 1, 0, 1],          [0, 1, 1, 0, 1],          [1, 0, 1, 0, 0],          [0, 0, 1, 0, 1],          [1, 0, 1, 1, 1] ] check_celebrity(grid)

十八、排序

二进制搜索只对有序列表有效。那么程序如何按顺序得到一个列表呢?当用户点击一个列标题或者需要排序的东西时,程序如何排序一个条目列表?

有几种算法可以做到这一点。两种最简单的排序算法是选择排序和插入排序。也存在其他排序算法,比如 shell、merge、heap 和快速排序。

了解这些种类如何工作的最好方法是观察它们。要查看常用的排序算法,请访问这个优秀的网站:

http://www.sorting-algorithms.com

每一种都有优点和缺点。有些人会很快对列表进行排序,如果列表几乎是按顺序排列的话。如果列表是完全随机的,有些人会快速排序。其他列表排序很快,但占用更多内存。理解排序是如何工作的对于为你的程序选择合适的排序是很重要的。

交换值

在学习排序之前,我们需要学习如何在两个变量之间交换值。这是很多排序算法中的常见操作。假设一个程序有一个如下所示的列表:

my_list = [15,57,14,33,72,79,26,56,42,40]

开发者想要交换位置 0 和 2,它们分别包含数字 15 和 14。见下图。

A978-1-4842-1790-0_18_Figa_HTML.jpg

交换数组中的值

第一次尝试编写这段代码可能如下所示:

my_list[0] = my_list[2]

my_list[2] = my_list[0]

A978-1-4842-1790-0_18_Figb_HTML.jpg

交换数组值的尝试不正确

请看上图,了解一下会发生什么。这显然行不通。第一次赋值list[0] = list[2]导致位置 0 中的值 15 被位置 2 中的值 14 覆盖,并且不可恢复地丢失。带有list[2] = list[0]的下一行只是将 14 复制回单元格 2,它已经有了一个 14。

要解决这个问题,应该分三步来交换数组中的值。有必要创建一个临时变量来保存交换操作期间的值。见下图。进行交换的代码如下所示:

temp = my_list[0]

my_list[0] = my_list[2]

my_list[2] = temp

第一行将位置 0 的值复制到temp变量中。这允许代码用位置 2 的值覆盖位置 0,而不会丢失数据。最后一行获取位置 0 的旧值,当前保存在temp变量中,并将其放在位置 2。

A978-1-4842-1790-0_18_Figc_HTML.jpg

交换数组值的正确方法

选择排序

选择从查看元素 0 开始。然后,代码 next 从元素 1 到 n-1 扫描列表的其余部分,以找到最小的数字。最小的数字被交换到元素 0 中。然后代码移动到元素 1,然后是元素 2,依此类推。从图形上看,排序如下图所示。

A978-1-4842-1790-0_18_Figd_HTML.jpg

选择排序

选择排序的代码包含两个嵌套循环。外部循环跟踪代码希望将最小值交换到的当前位置。内部循环从当前位置开始,向右扫描寻找最小值。当它找到最小值时,交换就发生了。

def selection_sort(my_list):

""" Sort a list using the selection sort """

# Loop through the entire array

for cur_pos in range(len(my_list)):

# Find the position that has the smallest number

# Start with the current position

min_pos = cur_pos

# Scan left to right (end of the list)

for scan_pos in range(cur_pos + 1, len(my_list)):

# Is this position smallest?

if my_list[scan_pos] < my_list[min_pos]:

# It is, mark this position as the smallest

min_pos = scan_pos

# Swap the two values

temp = my_list[min_pos]

my_list[min_pos] = my_list[cur_pos]

my_list[cur_pos] = temp

外部循环将总是运行 n 次。内部循环将运行 \frac{n}{2} 次。无论列表是否有序,都会出现这种情况。通过在排序结束时代码进行交换之前检查min_poscur_pos是否相等,可以提高循环的效率。如果那些变量相等,就没有必要做那三行。

为了测试上面的选择排序代码,可以使用下面的代码。第一个函数将打印出列表。下一段代码将创建一个随机数列表,打印出来,排序,然后再打印一次。第 3 行的print语句将数字右对齐,使数字列更容易阅读。格式化打印报表将在第二十一章的中介绍。

# Before this code, paste the selection sort and import random

def print_list(my_list):

for item in my_list:

print("{:3}".format(item), end="")

print()

# Create a list of random numbers

my_list = []

for i in range(10):

my_list.append(random.randrange(100))

# Try out the sort

print_list(my_list)

selection_sort(my_list)

print_list(my_list)

在以下位置查看选择排序的动画:

http://www.sorting-algorithms.com/selection-sort

要获得真正独特的选择排序可视化效果,请在 YouTube 上搜索“选择排序舞蹈”或使用以下链接:

http://youtu.be/Ns4TPTC8whw

插入排序

在外部循环的工作方式上,插入排序类似于选择排序。插入排序从数组的左侧开始,一直到右侧。不同的是,插入排序并不选择最小的元素并将其放入适当的位置;插入排序选择已排序元素右侧的下一个元素。然后,它向上滑动每个较大的元素,直到到达要插入的正确位置。从图形上看,它类似于下图。

A978-1-4842-1790-0_18_Fige_HTML.jpg

插入排序

插入排序将列表分为两部分:排序的一半和未排序的一半。在每一轮外部循环中,算法将抓取下一个未排序的元素,并将其插入到列表中。

在下面的代码中,key_pos标记了列表中已排序和未排序部分的边界。该算法使用变量scan_pos扫描到key_pos的左侧。注意,在插入短消息中,scan_pos向下到左边,而不是向上到右边。大于key_value的每个单元格位置都上移(向右)一个位置。

当循环找到一个小于key_value的位置时,它停止并将key_value放在它的左边。

带有插入排序的外部循环将运行 n 次。如果循环被随机打乱,内部循环将平均运行 \frac{n}{2} 次。如果循环已经接近一个已排序的循环,那么内部循环不会运行太多,排序时间更接近 n。

def insertion_sort(my_list):

""" Sort a list using the insertion sort """

# Start at the second element (pos 1).

# Use this element to insert into the

# list.

for key_pos in range(1, len(my_list)):

# Get the value of the element to insert

key_value = my_list[key_pos]

# Scan from right to the left (start of list)

scan_pos = key_pos - 1

# Loop each element, moving them up until

# we reach the position the

while (scan_pos >= 0) and (my_list[scan_pos] > key_value):

my_list[scan_pos + 1] = my_list[scan_pos]

scan_pos = scan_pos - 1

# Everything’s been moved out of the way, insert

# the key into the correct location

my_list[scan_pos + 1] = key_value

在以下位置查看插入排序的动画:

http://www.sorting-algorithms.com/insertion-sort

如果你想了解另一种舞蹈,可以在 YouTube 上搜索“插入排序舞蹈”或者使用以下链接:

http://youtu.be/ROalU379l3U

回顾

多项选择测验

How many lines of code are normally used to swap two values? 3   2   4   5     What is key in writing code to properly swap two values? Using the swap operator.   Make sure you use the == operator rather than the = operator.   Using a variable to temporarily hold one of the values while swapping.     In the selection sort, what does the outside loop do? Selects the next element that we will be placing the smallest remaining value into.   Finds the smallest value in the list.   Counts the number of items in the list.     In the selection sort, what does the inside loop do? Selects the next element that we will be placing the smallest remaining value into.   Finds the smallest value in the list.   Counts the number of items in the list.     In the insertion sort, what does the outside loop do? Slides an element into a sorted position.   Selects the next element to be slid into a sorted position.   Finds the smallest value in the list.     In the insertion sort, what does the inside loop do? Slides an element into a sorted position.   Selects the next element to be slid into a sorted position.   Finds the smallest value in the list.     If the selection sort and insertion sort run in n2 time, what is n? The number of lines of code.   The number of elements to sort.   The time it takes to sort in milliseconds.   The number of lines of code.   The size of each element.     If the selection sort and insertion sort run in n2 time, what does that mean if I have a problem size of 100 (n = 100) and increase it by 10 times to n = 1000? The 1,000 elements will take about 1,000 times longer to sort than a list of 100 elements.   The 1,000 elements will take about 100 times longer to sort than a list of 100 elements.   The 1,000 elements will take about 10 times longer to sort than a list of 100 elements.   The 1,000 elements will take about 4 times longer to sort than a list of 100 elements.   The 1,000 elements will take about 2 times longer to sort than a list of 100 elements.     What type of list does the insertion sort work particularly well on? A list that already close to being in order.   A list that is in reverse order.   A randomly sorted list.    

简答工作表

Write code to swap the values 25 and 40. my_list = [55, 41, 52, 68, 45, 27, 40, 25, 37, 26]   Write code to swap the values 2 and 27. my_list = [27, 32, 18,  2, 11, 57, 14, 38, 19, 91]   Why does the following code not work? my_list = [70, 32, 98, 88, 92, 36, 81, 83, 87, 66] temp = list[0] my_list[1] = list[0] my_list[0] = temp   Show how the following numbers can be sorted using the selection sort. Show the numbers after each iteration of the outer loop, similar to what is shown earlier in the chapter where we showed how the numbers move around. I am not looking for a copy of the code to do the sort. 97   74    8   98   47   62   12   11    0   60   Show how the following numbers can be sorted using the selection sort: 74   92   18   47   40   58    0   36   29   25   Show how the following numbers can be sorted using the insertion sort. Note: The 0 will not be immediately sorted into place. If you think it should, go back and review how the insertion sort works again. 74   92   18   47   40   58    0   36   29   25   Show how the following numbers can be sorted using the insertion sort: 37   11   14   50   24    7   17   88   99    9   Explain what min_pos does in the selection sort.   Explain what cur_pos does in the selection sort.   Explain what scan_pos does in the selection sort.   Explain what key_pos and key_value are in the insertion sort.   Explain scan_pos in the insertion sort.   Look at the example sort program in the examples section here: http://ProgramArcadeGames.com/python_examples/f.php?file=sorting_examples.py Modify the sorts to print the number of times the inside loop is run and the number of times the outside loop is run. Modify the program to work with a list of 100. Paste the code you used here. Run the program and list the numbers you got here. (Don’t forget this part!)

十九、异常

当你的程序出错时,你想让用户看不到红色的 Python 错误信息吗?您想让您的程序不挂起吗?如果是这样,那么你需要异常。

异常用于处理代码执行过程中可能出现的异常情况。异常通常用于文件和网络操作。这使得代码能够优雅地处理磁盘空间不足、网络错误或权限错误。

词汇

处理异常时会用到几个术语和短语。以下是最常见的:

  • 异常:这个术语可能有两种意思。首先,导致异常程序流的条件。或者它可以用于引用表示数据条件的对象。每个异常都有一个保存相关信息的对象。
  • 异常处理:处理正常程序流程异常的过程。
  • Catch 块或异常块:处理异常情况的代码被称为捕获异常。
  • 抛出或抛出:当检测到程序流的异常情况时,创建一个异常对象的实例。然后将它抛出或引发给将捕获它的代码。
  • 未处理的异常或未捕获的异常:抛出但从未捕获的异常。这通常会导致错误和程序结束或崩溃。
  • Try 块:一组可能抛出异常的代码。

大多数编程语言都使用抛出和捕捉这两个术语。不幸的是 Python 没有。Python 使用 raise 和 exception。我们在这里介绍抛出/捕捉词汇,因为它们是行业中最流行的术语。

异常处理

处理异常的代码很简单。请参见下面的示例:

# Divide by zero

try:

x = 5 / 0

except:

print("Error dividing by zero")

第 2 行是try语句。它下面的每一行都是 try 块的一部分。在try块下面可能没有不以except语句开始的未缩进代码。try语句定义了代码试图执行的一段代码。

如果在代码处理过程中出现任何异常,执行将立即跳转到 catch 块。该代码块缩进在第 4 行的except语句下。这段代码负责处理错误。

程序可以使用异常来捕捉从文本到数字转换过程中出现的错误。例如:

# Invalid number conversion

try:

x = int("fred")

except:

print("Error converting fred to a number")

第 3 行将抛出一个异常,因为"fred"不能被转换成整数。第 5 行的代码将打印出一条错误消息。

下面是这个例子的扩展版本。它对用户的输入进行错误检查,以确保输入的是整数。如果用户不输入整数,程序会一直要求输入。代码使用异常处理来捕获可能发生在第 5 行的转换错误。如果用户输入的不是整数,当在第 5 行转换为数字时会抛出异常。如果第 5 行出现异常,第 6 行将number_entered设置为True的代码将不会运行。

number_entered = False

while not number_entered:

number_string = input("Enter an integer: ")

try:

n = int(number_string)

number_entered = True

except:

print("Error, invalid integer")

文件在操作过程中特别容易出错。磁盘可能会被填满,用户可能会删除正在写入的文件,文件可能会被移动,或者 USB 驱动器可能会在操作过程中被拔出。使用异常处理也可以很容易地捕获这些类型的错误。

# Error opening file

try:

my_file = open("myfile.txt")

except:

print("Error opening file")

可以捕获多种类型的错误,并以不同的方式进行处理。向用户提供比简单的“发生了错误”更准确的错误信息会很有用

在下面的代码中,不同类型的错误可能发生在try块中。通过将IOError放在except之后,该代码将只处理关于输入和输出(IO)的错误。同样,由于ValueError,下一个except模块仅处理转换值的误差,下一个模块覆盖除以零的误差。最后的异常处理发生在最后两行。由于该except块不包括特定类型的错误,所以它将处理上述先前的except块未涵盖的任何错误。总括except必须总是最后。

# Multiple errors

try:

my_file = open("myfile.txt")

my_line = my_file.readline()

my_int = int(s.strip())

my_calculated_value = 101 / my_int

except IOError:

print("I/O error")

except ValueError:

print("Could not convert data to an integer.")

except ZeroDivisionError:

print("Division by zero error")

except:

print("Unexpected error")

可从以下网址获得内置异常列表: http://docs.python.org/library/exceptions.html

示例:保存高分

这显示了如何在游戏之间保存高分。分数存储在一个名为high_score.txt的文件中。

"""

Show how to use exceptions to save a high score for a game.

Sample Python/Pygame Programs

http://programarcadegames.com/

"""

def get_high_score():

# Default high score

high_score = 0

# Try to read the high score from a file

try:

high_score_file = open("high_score.txt", "r")

high_score = int(high_score_file.read())

high_score_file.close()

print("The high score is", high_score)

except IOError:

# Error reading file, no high score

print("There is no high score yet.")

except ValueError:

# There’s a file there, but we don’t understand the number.

print("I’m confused. Starting with no high score.")

return high_score

def save_high_score(new_high_score):

try:

# Write the file to disk

high_score_file = open("high_score.txt", "w")

high_score_file.write(str(new_high_score))

high_score_file.close()

except IOError:

# Hm, can’t write it.

print("Unable to save the high score.")

def main():

""" Main program is here. """

# Get the high score

high_score = get_high_score()

# Get the score from the current game

current_score = 0

try:

# Ask the user for his/her score

current_score = int(input("What is your score? "))

except ValueError:

# Error, can’t turn what they typed into a number

print("I don’t understand what you typed.")

# See if we have a new high score

if current_score > high_score:

# We do! Save to disk

print("Yea! New high score!")

save_high_score(current_score)

else:

print("Better luck next time.")

# Call the main function, start up the game

if __name__ == "__main__":

main()

异常对象

关于错误的更多信息可以从异常对象中提取。在使用as关键字捕获错误时,可以检索这个对象。例如:

try:

x = 5 / 0

except ZeroDivisionError as e:

print(e)

e变量指向可以打印出来的关于异常的更多信息。使用异常对象可以做更多的事情,但不幸的是这超出了本章的范围。有关异常对象的详细信息,请查看 Python 在线文档。

异常生成

使用raise命令可能会产生异常。例如:

# Generating exceptions

def get_input():

user_input = input("Enter something: ")

if len(user_input) == 0:

raise IOError("User entered nothing")

getInput()

尝试使用上面的代码,并为引发的IOError添加异常处理。

也可以创建自定义异常,但这也超出了本书的范围。好奇的读者可以访问以下网站了解更多信息:

http://docs.python.org/tutorial/errors.html#raising-exceptions

正确的异常使用

if语句可以轻松处理条件时,不应该使用异常。正常代码在运行快乐路径场景时不应该引发异常。构造良好的 try/catch 代码很容易理解,但是涉及许多异常和跳转到不同处理程序的代码可能是调试的噩梦。(有一次,我被分配去调试读取 XML 文档的代码。它为所读取的文件的每一行生成了许多异常。它慢得令人难以置信,而且容易出错。该代码在读取文件的正常过程中应该从未生成过任何异常)。

回顾

多项选择测验

What is an exception? Something that results in abnormal program flow.   An if statement that is False.   Code that handles an unexpected condition in the program.   Paramore thinks you are the only one.     What is a catch or exception block? Something computers play in the back yard.   Code that may cause an error that needs to be handled.   Code that handles an unexpected condition in the program.   A block with 22 lines in it.     What is a try block? Something that results in abnormal program flow.   An if statement that is False.   Code that handles an unexpected condition in the program.   There is no try block, only do or not do blocks.   Code that may cause an error that needs to be handled.     What will print for x? try:         x = 5/0         y = 10 except:         print("Error") print(x) 5/0   Infinity   5   0 because the error has been caught.   x will not print, there is an error.     What does the keyword raise do? Checks for errors.   Brings code back to life after it has been executed.   Generates a new exception that will be handled by a try block.   Generates a new exception that will be handled by an except block.     What will print for y? try:         x = 5/0         y = 10 except:         print("Error") print(y) 10   Infinity   5   10 because the error has been caught.   y will not print, there is an error.     What is e? try:         x = 5 / 0 except ZeroDivisionError as e:         print(e) 5   0   An object that stores data about the error.   A class that stores data about the error.   A library for exception handling.    

简答工作表

Define the following terms in your own words. Don’t just copy/paste from the book:

  • 异常
  • 异常处理
  • 尝试阻止
  • 捕捉块
  • 未处理异常情况

  Show how to modify the following code so that an error is printed if the number conversion is not successful. Modify this code; don’t just copy the example from the text. No need to ask again if the conversion is unsuccessful. user_input_string = input("Enter a number:") user_value = int(user_input_string)   What will the following code output? Predict, and then run the code to see if you are correct. Write your prediction here and if you are right. If you aren’t, make sure you understand why. (Make sure to write both the prediction, and the actual results. If the program raises an error, list that fact for this and the next problem as well.) x = 5 y = 0 print("A") try:     print("B")     a = x / y     print("C") except:     print("D") print("E") print(a)   What will the following code output? Predict, and then run the code to see if you are correct. Write your prediction here and if you are right. If you aren’t, make sure you understand why. x = 5 y = 10 print("A") try:     print("B")     a = x / y     print("C") except:     print("D") print("E") print(a)

二十、递归

A child couldn't sleep. Her mother told her the story of a little frog. The little frog couldn't sleep. The mother frog told her the story of a bear. The bear couldn't sleep. The mother bear told her the story of a little weasel ... The little weasel fell asleep. .... the bear is asleep; ... the little frog is asleep; .... the child is asleep. (Source: http://everything2.com/title/recursion )

递归是根据自身定义的对象或过程。像阶乘和斐波纳契数列这样的数学模式是递归的。可以包含其他文档的文档(这些文档本身可以包含其他文档)是递归的。分形图像甚至某些生物过程在工作方式上都是递归的。

递归用在哪里?

文档,比如网页,自然是递归的。例如,下图显示了一个 web 文档。

A978-1-4842-1790-0_20_Figa_HTML.jpg

网页

该 web 文档可以包含在一个框中,这有助于页面布局,如下图所示。

A978-1-4842-1790-0_20_Figb_HTML.jpg

带有表格的网页

这是递归的。每个框可以包含一个网页,网页可以有一个框,框可以包含另一个网页,如图所示。

A978-1-4842-1790-0_20_Figc_HTML.jpg

递归网页

递归函数通常与高级搜索和排序算法一起使用。我们将在这里展示一些,如果你决定学习数据结构,你将会看到更多。

即使一个人没有成为程序员,理解递归系统的概念也是很重要的。如果业务上需要递归表结构、文档或其他东西,知道如何事先向程序员说明这一点是很重要的。

例如,一个人可能指定一个关于食谱的 web 程序需要支持配料和方向的能力。熟悉递归的人可能会说,每种成分本身都可能是其他成分的配方(这可能是配方。)第二个系统要强大得多。

递归是如何编码的?

在前面的章节中,我们已经使用了调用其他函数的函数。例如:

def f():

g()

print("f")

def g():

print("g")

f()

函数也有可能调用自己。一个调用自身的函数使用了一个叫做递归的概念。例如:

def f():

print("Hello")

f()

f()

上面的例子将打印Hello,然后再次调用f()函数。这将导致另一个Hello被打印出来,并再次调用f()函数。这种情况会一直持续下去,直到计算机耗尽所谓的堆栈空间。发生这种情况时,Python 将输出一个很长的错误,以下列内容结束:

RuntimeError: maximum recursion depth exceeded

电脑在告诉你,程序员,你在兔子洞里走得太远了。

控制递归深度

为了成功地使用递归,需要有一种方法来防止函数无休止地一遍又一遍地调用自己。下面的例子计算了它被调用的次数,并使用一个if语句在函数调用自己 10 次后退出。

def f(level):

# Pring the level we are at

print("Recursion call, level",level)

# If we haven’t reached level ten…

if level < 10:

# Call this function again

# and add one to the level

f(level+1)

# Start the recursive calls at level 1

f(1)

Recursion call, level 1

Recursion call, level 2

Recursion call, level 3

Recursion call, level 4

Recursion call, level 5

Recursion call, level 6

Recursion call, level 7

Recursion call, level 8

Recursion call, level 9

Recursion call, level 10

递归阶乘计算

任何可以递归完成的代码都可以不使用递归来完成。一些程序员觉得递归代码更容易理解。

计算一个数的阶乘是使用递归的典型例子。阶乘在概率和统计中很有用。例如:

 10!=10\cdot 9\cdot 8\cdot 7\cdot 6\cdot 5\cdot 4\cdot 3\cdot 2\cdot 1

递归地,这可以描述为:

 n!=\left\{\begin{array}{cc}\hfill 1\hfill & \hfill if\kern0.5em n=0,\hfill \\ {}\hfill n\cdot \left(n-1\right)!\hfill & \hfill if\kern0.5em n>0.\hfill \end{array}\right.

下面是计算 n 的两个示例函数!。第一个是非递归的;第二个是递归的。

# This program calculates a factorial

# WITHOUT using recursion

def factorial_nonrecursive(n):

answer = 1

for i in range(2, n + 1):

answer = answer * i

return answer

# This program calculates a factorial

# WITH recursion

def factorial_recursive(n):

if n <= 1:

return 1

else:

return n * factorial_recursive(n - 1)

这些函数本身什么也不做。下面是一个例子,我们把它放在一起。这个例子还在函数中添加了一些print语句,这样我们就可以看到发生了什么。

# This program calculates a factorial

# WITHOUT using recursion

def factorial_nonrecursive(n):

answer = 1

for i in range(2, n + 1):

print(i, "*", answer, "=", i * answer)

answer = answer * i

return answer

print("I can calculate a factorial!")

user_input = input("Enter a number:")

n = int(user_input)

answer = factorial_nonrecursive(n)

print(answer)

# This program calculates a factorial

# WITH recursion

def factorial_recursive(n):

if n == 1:

return n

else:

x = factorial_recursive(n - 1)

print( n, "*", x, "=", n * x )

return n * x

print("I can calculate a factorial!")

user_input = input("Enter a number:")

n = int(user_input)

answer = factorial_recursive(n)

print(answer)

I can calculate a factorial!

Enter a number:7

2 * 1 = 2

3 * 2 = 6

4 * 6 = 24

5 * 24 = 120

6 * 120 = 720

7 * 720 = 5040

5040

I can calculate a factorial!

Enter a number:7

2 * 1 = 2

3 * 2 = 6

4 * 6 = 24

5 * 24 = 120

6 * 120 = 720

7 * 720 = 5040

5040

递归矩形

递归非常适合处理本身是递归的结构化文档。例如,web 文档可以有一个分成行和列的表格来帮助布局。一行可能是页眉,另一行是正文,最后是页脚。表格单元格内可能是另一个表格。其中还可以存在另一个表。

另一个例子是电子邮件。可以将他人的电子邮件附加到您自己的电子邮件中。但是该电子邮件可能附有另一封电子邮件,以此类推。

我们能在我们的 pygame 程序中直观地看到递归吗?没错。图中显示了一个示例程序,该程序绘制了一个矩形,并递归地保持在矩形内绘制矩形。每个矩形比父矩形小 20%。看代码。密切注意recursive_draw函数中的递归调用。

A978-1-4842-1790-0_20_Figd_HTML.jpg

递归矩形

"""

Recursively draw rectangles.

Sample Python/Pygame Programs

http://programarcadegames.com/

"""

import pygame

# Colors

BLACK = (0, 0, 0)

WHITE = (255, 255, 255)

def recursive_draw(x, y, width, height):

""" Recursive rectangle function. """

pygame.draw.rect(screen, BLACK,

[x, y, width, height],

1)

# Is the rectangle wide enough to draw again?

if(width > 14):

# Scale down

x += width * .1

y += height * .1

width *= .8

height *= .8

# Recursively draw again

recursive_draw(x, y, width, height)

pygame.init()

# Set the height and width of the screen

size = [700, 500]

screen = pygame.display.set_mode(size)

pygame.display.set_caption("My Game")

# Loop until the user clicks the close button.

done = False

# Used to manage how fast the screen updates

clock = pygame.time.Clock()

# -------- Main Program Loop -----------

while not done:

for event in pygame.event.get():

if event.type == pygame.QUIT:

done = True

# Set the screen background

screen.fill(WHITE)

# ALL CODE TO DRAW SHOULD GO BELOW THIS COMMENT

recursive_draw(0, 0, 700, 500)

# ALL CODE TO DRAW SHOULD GO ABOVE THIS COMMENT

# Go ahead and update the screen with what we’ve drawn.

pygame.display.flip()

# Limit to 60 frames per second

clock.tick(60)

# Be IDLE friendly. If you forget this line, the program will ’hang’

# on exit.

pygame.quit()

分形

分形是递归定义的。这是一个非常简单的分形,展示了它是如何根据递归的深度而变化的。

A978-1-4842-1790-0_20_Fige_HTML.jpg

递归分形等级 0

A978-1-4842-1790-0_20_Figf_HTML.jpg

递归分形级别 1

A978-1-4842-1790-0_20_Figg_HTML.jpg

递归分形层次 2

A978-1-4842-1790-0_20_Figh_HTML.jpg

递归分形等级 3

"""

Sample fractal using recursion.

Sample Python/Pygame Programs

http://programarcadegames.com/

"""

import pygame

# Define some colors

black = (0, 0, 0)

white = (255, 255, 255)

green = (0, 255, 0)

red = (255, 0, 0)

def recursive_draw(x, y, width, height, count):

# Draw the rectangle

# pygame.draw.rect(screen,black,[x,y,width,height],1)

pygame.draw.line(screen,

black,

[x + width*.25, height // 2 + y],

[x + width*.75, height // 2 + y],

3)

pygame.draw.line(screen,

black,

[x + width * .25, (height * .5) // 2 + y],

[x + width * .25,  (height * 1.5) // 2 + y],

3)

pygame.draw.line(screen,

black,

[x + width * .75, (height * .5) // 2 + y],

[x + width * .75, (height * 1.5) // 2 + y],

3)

if count > 0:

count -= 1

# Top left

recursive_draw(x, y, width // 2, height // 2, count)

# Top right

recursive_draw(x + width // 2, y, width // 2, height // 2, count)

# Bottom left

recursive_draw(x, y + width // 2, width // 2, height // 2, count)

# Bottom right

recursive_draw(x + width // 2, y + width // 2, width // 2, height // 2, count)

pygame.init()

# Set the height and width of the screen

size = [700, 700]

screen = pygame.display.set_mode(size)

pygame.display.set_caption("My Game")

# Loop until the user clicks the close button.

done = False

# Used to manage how fast the screen updates

clock = pygame.time.Clock()

# -------- Main Program Loop -----------

while not done:

for event in pygame.event.get():

if event.type == pygame.QUIT:

done = True

# Set the screen background

screen.fill(white)

# ALL CODE TO DRAW SHOULD GO BELOW THIS COMMENT

fractal_level = 3

recursive_draw(0, 0, 700, 700, fractal_level)

# ALL CODE TO DRAW SHOULD GO ABOVE THIS COMMENT

# Go ahead and update the screen with what we’ve drawn.

pygame.display.flip()

# Limit to 20 frames per second

clock.tick(20)

# Be IDLE friendly. If you forget this line, the program will ’hang’

# on exit.

pygame.quit()

递归二分搜索法

递归也可以用来执行二分搜索法。下面是第十六章中的一个非递归二分搜索法:

def binary_search_nonrecursive(search_list, key):

lower_bound = 0

upper_bound = len(search_list) - 1

found = False

while lower_bound < upper_bound and found == False:

middle_pos = (lower_bound + upper_bound) // 2

if search_list[middle_pos] < key:

lower_bound = middle_pos + 1

elif list[middle_pos] > key:

upper_bound = middle_pos

else:

found = True

if found:

print( "The name is at position",middle_pos)

else:

print( "The name was not in the list." )

binary_search_nonrecursive(name_list,"Morgiana the Shrew")

同样的二分搜索法以递归的方式写道:

def binary_search_recursive(search_list, key, lower_bound, upper_bound):

middle_pos = (lower_bound + upper_bound) // 2

if search_list[middle_pos] < key:

binary_search_recursive(search_list,

key,

middle_pos + 1,

upper_bound)

elif search_list[middle_pos] > key:

binary_search_recursive(search_list,

key,

lower_bound,

middle_pos )

else:

print("Found at position", middle_pos)

lower_bound = 0

upper_bound = len(name_list) - 1

binary_search_recursive(name_list,

"Morgiana the Shrew",

lower_bound,

upper_bound)

回顾

简答工作表

“To understand recursion, one must first understand recursion.” Explain the joke.   Two mirrors face each other. Explain how their reflections demonstrate the property of recursion.   Explain how Multi-Level Marketing uses recursion.   Explain how the sweep function in the classic minesweeper game could be done with recursion.   Explain how finding your way out of a maze could be done with recursion.   Use the Chrome browser and create your own screenshot at: http://juliamap.googlelabs.com Use your mouse and mouse wheel to zoom into an interesting part of the fractal.   Write a recursive function f(n) that takes in a value n and returns the value for f, given the definition below.

 {f}_n=\left\{\begin{array}{c}\hfill 6\hfill \\ {}\hfill \frac{1}{2}{f}_{n-1}+4\hfill \end{array}\kern1.5em \begin{array}{c}\hfill if\kern0.5em n=1,\hfill \\ {}\hfill if\kern0.5em n>1.\hfill \end{array}\right.

Then write a for loop that prints out the answers for values of n from 1 to 10. It should look like: n= 1, a= 6 n= 2, a= 7.0 n= 3, a= 7.5 n= 4, a= 7.75 n= 5, a= 7.875 n= 6, a= 7.9375 n= 7, a= 7.96875 n= 8, a= 7.984375 n= 9, a= 7.9921875 n= 10, a= 7.99609375 The function should not have a print statement inside it, nor a loop. The for loop that is written should be outside the function and call the function to get the results and print them. Write recursive code that will print out the first 10 terms of the sequence below.

 {f}_n=\left\{\begin{array}{c}\hfill \begin{array}{l}1\\ {}1\end{array}\hfill \\ {}\hfill f\left(n-1\right)+f\left(n-2\right)\hfill \end{array}\kern1.5em \begin{array}{c}\hfill \begin{array}{l} if\kern0.5em n=1\\ {} if\kern0.5em n=2\end{array}\hfill \\ {}\hfill if\kern0.5em n>2\hfill \end{array}\right.