>>> from env_helper import info; info()
待更新

5.3. 使用数据结构对真实世界建模

甚至在因特网之前,人们也有办法与世界另一边的某人下一盘国际象棋。 每个棋手在自己家里放好一个棋盘,然后轮流向对方寄出明信片, 描述每一着棋。要做到这一点,棋手需要一种方法, 无二义地描述棋盘的状态,以及他们的着法。 在“代数记谱法”中,棋盘空间由一个数字和字母坐标确定, 如图5-1所示。

_images/img5_1.png

图5-1 代数记谱法中棋盘的坐标

棋子用字母表示: K 表示王, Q 表示后, R 表示车,B 表示象, N 表示马。 描述一次移动,用棋子的字母和它的目的地坐标。 一对这样的移动表示一个回合(白方先下), 例如,棋谱2.Nf3 Nc6表明在棋局的第二回合, 白方将马移动到f3,黑方将马移动到c6。

代数记谱法还有更多内容,但要点是你可以用它无二义地描述象棋游戏, 不需要站在棋盘前。你的对手甚至可以在世界的另一边! 实际上,如果你的记忆力很好,甚至不需要物理的棋具: 只需要阅读寄来的棋子移动,更新心里想的棋盘。

计算机有很好的记忆力。现在计算机上的程序, 很容易存储几百万个像'2. Nf3 Nc6' 这样的字符串。 这就是为什么计算机不用物理棋盘就能下象棋。 它们用数据建模来表示棋盘, 你可以编写代码来使用这个模型。

这里就可以用到列表和字典。可以用它们对真实世界建模, 例如棋盘。作为第一个例子, 我们将使用比国际象棋简单一点的游戏:井字棋。

5.3.1. 井字棋盘

井字棋盘看起来像一个大的井字符号(#),有9个空格, 可以包含 X 、0或空。 要用字典表示棋盘,可以为每个空格分配一个字符串键, 如图5-2所示。

_images/img5_2.png

图5-2井字棋盘的空格和它们对应的键

可以用字符串值来表示,棋盘上每个空格有什么: 'X''0' 或空格字符)。因此, 需要存储9个字符串。可以用一个字典来做这事。 带有键 'top-R' 的字符串表示右上角, 带有键 'low-L' 的字符串表示左下角, 带有键 'mid-M' 的字符串表示中间,以此类推。

这个字典就是表示井字棋盘的数据结构。 将这个字典表示的棋盘保存在名为 theBoard 的变量中。打开一个文件编辑器窗口, 输入以下代码,并保存为 ticTacToe.py

>>> theBoard = {'top-L': ' ', 'top-M': ' ', 'top-R': ' ',
>>> 'mid-L': ' ', 'mid-M': ' ', 'mid-R': ' ',
>>> 'low-L': ' ', 'low-M': ' ', 'low-R': ' '}

保存在 theBoard 变量中的数据结构, 表示了图5-3中的井字棋盘。

_images/img5_3.png

图5-3 一个空的井字棋盘

因为 theBoard 变量中每个键的值都是单个空格字符, 所以这个字典表示一个完全干净的棋盘。 如果玩家 X 选择了中间的空格, 就可以用下面这个字典来表示棋盘:

>>> theBoard = {'top-L': ' ', 'top-M': ' ', 'top-R': ' ',
>>> 'mid-L': ' ', 'mid-M': 'X', 'mid-R': ' ',
>>> 'low-L': ' ', 'low-M': ' ', 'low-R': ' '}

theBoard变量中的数据结构现在表示图5-4中的井字棋盘。

_images/img5_4.png

图5-4第一着

一个玩家O获胜的棋盘上,他将O横贯棋盘的顶部,看起来像这样:

>>> theBoard = {'top-L': 'O', 'top-M': 'O', 'top-R': 'O',
>>> 'mid-L': 'X', 'mid-M': 'X', 'mid-R': ' ',
>>> 'low-L': ' ', 'low-M': ' ', 'low-R': 'X'}

theBoard变量中的数据结构现在表示图5-5中的井字棋盘。

_images/img5_5.png

图5-5玩家O获胜

当然,玩家只看到打印在屏幕上的内容,而不是变量的内容。 让我们创建一个函数,将棋盘字典打印到屏幕上。 将下面代码添加到 ticTacToe.py (新代码是黑体的):

>>> theBoard = { 'top-L':' ','top-M':' ','top-R':' ',
>>>              'mid-L':' ','mid-M':' ','mid-R':' ',
>>>              'low-L':' ','low-M':' ','low-R':' ',
>>>            }
>>>
>>> def printBoard(board):
>>>      print(board['top-L'] + '|' + board['top-M'] + '|' + board['top-R'])
>>>      print('-+-+-')
>>>      print(board['mid-L'] + '|' + board['mid-M'] + '|' + board['mid-R'])
>>>      print('-+-+-')
>>>      print(board['low-L'] + '|' + board['low-M'] + '|' + board['low-R'])
>>>
>>> printBoard(theBoard)
 | |
-+-+-
 | |
-+-+-
 | |

运行这个程序时, printBoard() 将打印出空白井字棋盘。

printBoard() 函数可以处理传入的任何井字棋数据结构。 尝试将代码改成以下的样子:

>>> theBoard ={'top-L':'O','top-M':'O', 'top-R':'O', 'mid-L': 'X', 'mid-M':'X', 'mid-R':' ', 'low-L': ' ','low-M': ' ', 'low-R': 'X'}
>>> def printBoard(board):
>>>     print(board['top-L'] + '|'+ board['top-M']+ '|'+ board['top-R'])
>>>     print('-+-+-')
>>>     print(board['mid-L'] + '|'+ board['mid-M']+ '|'+ board['mid-R'])
>>>     print('-+-+-')
>>>     print(board['low-L'] + '|'+ board['low-M']+ '|'+ board['low-R'] )
>>> printBoard(theBoard)
O|O|O
-+-+-
X|X|
-+-+-
 | |X

现在运行该程序,新棋盘将打印在屏幕上。

因为你创建了一个数据结构来表示井字棋盘, 编写了 printBoard() 中的代码来解释该数据结构, 所以就有了一个程序,对井字棋盘进行了“建模”。 也可以用不同的方式组织数据结构 (例如,使用 'TOP-LEFT' 这样的键来代替 'top-L' ), 但只要代码能处理你的数据结构,就有了正确工作的程序。

例如, printBoard() 函数预期井字棋数据结构是一个字典, 包含所有9个空格的键。假如传入的字典缺少 'mid-L' 键, 程序就不能工作了。

theBoard ={'top-L':'O','top-M':'O', 'top-R':'O', 'mid-M':'X', 'mid-R':' ', 'low-L': ' ','low-M': ' ', 'low-R': 'X'}
def printBoard(board):
    print(board['top-L'] + '|'+ board['top-M']+ '|'+ board['top-R'])
    print('-+-+-')
    print(board['mid-L'] + '|'+ board['mid-M']+ '|'+ board['mid-R'])
    print('-+-+-')
    print(board['low-L'] + '|'+ board['low-M']+ '|'+ board['low-R'] )
printBoard(theBoard)

O|O|O
-+-+-

---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-12-1856f64a67d7> in <module>()
      6     print('-+-+-')
      7     print(board['low-L'] + '|'+ board['low-M']+ '|'+ board['low-R'] )
----> 8 printBoard(theBoard)

<ipython-input-12-1856f64a67d7> in printBoard(board)
      3     print(board['top-L'] + '|'+ board['top-M']+ '|'+ board['top-R'])
      4     print('-+-+-')
----> 5     print(board['mid-L'] + '|'+ board['mid-M']+ '|'+ board['mid-R'])
      6     print('-+-+-')
      7     print(board['low-L'] + '|'+ board['low-M']+ '|'+ board['low-R'] )

KeyError: 'mid-L'

现在让我们添加代码,允许玩家输入他们的着法。修改 ticTacToe.py 程序如下所示:

>>> theBoard = { 'top-L':' ','top-M':' ','top-R':' ',
>>>              'mid-L':' ','mid-M':' ','mid-R':' ',
>>>              'low-L':' ','low-M':' ','low-R':' ',
>>>            }
>>>
>>> def printBoard(board):
>>>      print(board['top-L'] + '|' + board['top-M'] + '|' + board['top-R'])
>>>      print('-+-+-')
>>>      print(board['mid-L'] + '|' + board['mid-M'] + '|' + board['mid-R'])
>>>      print('-+-+-')
>>>      print(board['low-L'] + '|' + board['low-M'] + '|' + board['low-R'])
>>>
>>> turn = 'X'
>>> for i in range(9):
>>>     printBoard(theBoard)
>>>     print('Turn for ' + turn + '.Move on which space')
>>>     move = input()
>>>     theBoard[move] = turn
>>>     if turn == 'X':
>>>         turn = 'O'
>>>     else:
>>>         turn = 'X'
>>> printBoard(theBoard)
 | |
-+-+-
 | |
-+-+-
 | |
Turn for X.Move on which space
X
 | |
-+-+-
 | |
-+-+-
 | |
Turn for O.Move on which space
top-L
O| |
-+-+-
 | |
-+-+-
 | |
Turn for X.Move on which space

O| |
-+-+-
 | |
-+-+-
 | |
Turn for O.Move on which space

O| |
-+-+-
 | |
-+-+-
 | |
Turn for X.Move on which space

O| |
-+-+-
 | |
-+-+-
 | |
Turn for O.Move on which space

O| |
-+-+-
 | |
-+-+-
 | |
Turn for X.Move on which space

O| |
-+-+-
 | |
-+-+-
 | |
Turn for O.Move on which space
X
O| |
-+-+-
 | |
-+-+-
 | |
Turn for X.Move on which space
v
O| |
-+-+-
 | |
-+-+-
 | |

这不是一个完整的井字棋游戏(例如,它并不检查玩家是否获胜), 但这已足够展示如何在程序中使用数据结构。

注意 你很好奇,完整的井字棋程序的源代码在网上有介绍,网址是http://nostarch.com/automatestuff/ 。

5.3.2. 嵌套的字典和列表

对井字棋盘建模相当简单:棋盘只需要一个字典,包含9 个键值对。 当你对复杂的事物建模时,可能发现字典和列表中需要包含其他字典和列表。 列表适用于包含一组有序的值,字典适合于包含关联的键与值。 例如,下面的程序使用字典包含其他字典,用于记录谁为野餐带来了什么食物。 totalBrought() 函数可以读取这个数据结构, 计算所有客人带来的食物的总数。

>>> allGuests = {'Alice': {'apples': 5, 'pretzels': 12},
>>>             'Bob': {'ham sandwiches': 3, 'apples': 2},
>>>             'Carol': {'cups': 3, 'apple pies': 1}}
>>> def totalBrought(guests, item):
>>>     numBrought = 0
>>>     for k, v in guests.items():
>>>         numBrought = numBrought + v.get(item,0)
>>>     return numBrought
>>> print('Number of things being brought:')
>>> print(' - Apples ' + str(totalBrought(allGuests, 'apples')))
>>> print(' - Cups ' + str(totalBrought(allGuests, 'cups')))
>>> print(' - Cakes ' + str(totalBrought(allGuests, 'cakes')))
>>> print(' - Ham Sandwiches ' + str(totalBrought(allGuests, 'ham sandwiches')))
>>> print(' - Apple Pies ' + str(totalBrought(allGuests, 'apple pies')))
Number of things being brought:
 - Apples 7
 - Cups 3
 - Cakes 0
 - Ham Sandwiches 3
 - Apple Pies 1

这似乎对一个非常简单的东西建模,你可能认为不需要费事去写一个程序来做 到这一点。但是要认识到,这个函数 totalBrought() 可以轻易地处理一个字典, 其中包含数千名客人,每个人都带来了 “数千种”不同的野餐食物。 这样用这种数据结构来保存信息, 并使用 totalBrought() 函数,就会节约大量的时间!

你可以用自己喜欢的任何方法,用数据结构对事物建模,只要程序中其他代码能够 正确处理这个数据模型。在刚开始编程时, 不需要太担心数据建模的“正确”方式。 随着经验增加,你可能会得到更有效的模型, 但重要的是,该数据模型符合程序的需要。

5.3.3. 小结

在本章中,你学习了字典的所有相关知识。列表和字典是这样的值, 它们可以包含多个值,包括其他列表和字典。字典是有用的, 因为你可以把一些项(键)映射到另一些项(值), 它不像列表,只包含一系列有序的值。字典中的值是通过方括号访问的, 像列表一样。字典不是只能使用整数下标,而是可以用各种数据类型作为键: 整型、浮点型、字符串或元组。通过将程序中的值组织成数据结构, 你可以创建真实世界事物的模型。井字棋盘就是这样一个例子。

这就介绍了Python 编程的所有基本概念!在本书后面的部分, 你将继续学习一些新概念,但现在你已学习了足够多的内容, 可以开始编写一些有用的程序,让一些任务自动化。 你可能不觉得自己有足够的 Python 知识,来实现页面下载、 更新电子表格,或发送文本消息。但这就是 Python 模块要干的事! 这些模块由其他程序员编写,提供了一些函数,让这些事情变得容易。 所以让我们学习如何编写真正的程序,实现有用的自动化任务。