>>> from env_helper import info; info()
待更新
5.3. 使用数据结构对真实世界建模¶
甚至在因特网之前,人们也有办法与世界另一边的某人下一盘国际象棋。 每个棋手在自己家里放好一个棋盘,然后轮流向对方寄出明信片, 描述每一着棋。要做到这一点,棋手需要一种方法, 无二义地描述棋盘的状态,以及他们的着法。 在“代数记谱法”中,棋盘空间由一个数字和字母坐标确定, 如图5-1所示。
图5-1 代数记谱法中棋盘的坐标
棋子用字母表示: K 表示王, Q 表示后, R 表示车,B 表示象, N 表示马。 描述一次移动,用棋子的字母和它的目的地坐标。 一对这样的移动表示一个回合(白方先下), 例如,棋谱2.Nf3 Nc6表明在棋局的第二回合, 白方将马移动到f3,黑方将马移动到c6。
代数记谱法还有更多内容,但要点是你可以用它无二义地描述象棋游戏, 不需要站在棋盘前。你的对手甚至可以在世界的另一边! 实际上,如果你的记忆力很好,甚至不需要物理的棋具: 只需要阅读寄来的棋子移动,更新心里想的棋盘。
计算机有很好的记忆力。现在计算机上的程序,
很容易存储几百万个像'2. Nf3 Nc6'
这样的字符串。
这就是为什么计算机不用物理棋盘就能下象棋。 它们用数据建模来表示棋盘,
你可以编写代码来使用这个模型。
这里就可以用到列表和字典。可以用它们对真实世界建模, 例如棋盘。作为第一个例子, 我们将使用比国际象棋简单一点的游戏:井字棋。
5.3.1. 井字棋盘¶
井字棋盘看起来像一个大的井字符号(#),有9个空格, 可以包含 X 、0或空。 要用字典表示棋盘,可以为每个空格分配一个字符串键, 如图5-2所示。
图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中的井字棋盘。
图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中的井字棋盘。
图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中的井字棋盘。
图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 模块要干的事! 这些模块由其他程序员编写,提供了一些函数,让这些事情变得容易。 所以让我们学习如何编写真正的程序,实现有用的自动化任务。