1.3 包(注意与python中的包区分)
本节以包为例子,继续阐述抽象数据类型。
包用于储存数据项的简单容器。具有如下性质
- 访问其中的具体某个数据项;
- 增删数据项;
- 判断一个数据项是否在包内;
- 遍历包内所有数据项。
1.3.1 包ADT
包这一ADT是用于储存数据项的容器,其中的数据项是可以重复的,无特定顺序,相互之间可比较。
- Bag():创建包实例;
- length():包内所含数据项的个数(重复数据项也计算在内);
- contains(item):判断数据项item是否在包内,返回True或False;
- add(item):向包内加入数据项item;
- remove(item):从包内移除数据项item,若item不在包内,则引发异常;
- iterator():返回用于遍历包内所有数据项的迭代器。
使用示例:
#-*-coding: utf-8-*-
from linearbag import Bag
from date import Date
# 获取用户输入的出生日期
def promptAndExtracDate():
print "Enter a birth date."
month = int(raw_input("month (0 to quit): "))
if month == 0:
return None
else:
day = int(raw_input("day: "))
year = int(raw_input("year: "))
return Date(month, day, year)
def main():
bornBefore = Date(6, 1, 1988)
bag = Bag()
date = promptAndExtracDate()
while date is not None:
bag.add(date)
date = promptAndExtracDate()
for date in bag:
if date <= bornBefore:
print "Is at least 21 years of age:", date
if __name__ == "__main__":
main()
为什么使用包ADT?而不是用python提供的列表?书上基本重复之前的那四点使用抽象数据类型的好处,这里不是很理解。
1.3.2 选择数据结构
选择适当的数据结构来实现包ADT。
需满足如下要求:
- 所选择的数据结构必须能储存ADT数据的所有可能值;
- 所选择的数据结构必须能提供访问和操作其内所有数据项的功能;
- 所选择的数据结构必须有利于操作的高效实现。
其中第三点与复杂度(complexity)相关,之后再考虑。
显然python中的列表和字典这两种数据结构能满足要求,列表自不必表。关于字典,可以使用数据项作为键,个数作为值,来实现。增删数据项,就是个数增减而已。当个数减至零时,自动删除这一键值对即可。考虑到,同等数据项的情况下,列表占用的内存是字典占用内存的一半,而大多数数据项的个数是1,因此列表是更好的选择。而如果多数数据项的个数大于1,则字典是更好的选择。
1.3.3 基于列表的实现
#-*-coding: utf-8-*-
# 使用列表实现“包”这一抽象数据类型
class Bag(object):
# 构造一个空包
def __init__(self):
self._theItems = list()
# 返回包中物品的个数
def __len__(self):
return len(self._theItems)
# 判断一个物品是否在包中
def __contains__(self, item):
return item in self._theItems
# 添加新物品
def add(self, item):
self._theItems.append(item)
# 删除包中的一个物品,并返回该物品
def remove(self, item):
assert item in self._theItems, "The item must be in the bag." # 必须首先断言物品item存在包中,否则引发断言异常
ndx = self._theItems.index(item)
return self._theItems.pop(ndx)
# 返回用于遍历该包的迭代器
def __iter__(self, item):
......
1.4 迭代器
大多数容器型ADT需要提供遍历功能,有两种方法。第一种是给用户提供访问底层数据结构的方法,但这违背了抽象原则,背离了使用ADT的目的。第二种就是使用迭代器。
迭代器是一种可遍历容器内所有数据项而无需暴露底层实现的机制。
1.4.1 设计迭代器
为了能使我们定义的ADT具有遍历功能,我们必须使用迭代器类,这一个类中包含__iter__和__next__两个特殊方法。
#-*-coding: utf-8-*-
# 包的迭代器
class _BagIterator(object):
def __init__(self, theList):
self._bagItems = theList
self._curItem = 0
def __iter__(self):
return self
def __next__(self):
if self._curItem < len(self._bagItems):
item = self._BagItems[self._curItem]
self._curItem += 1
return item
else:
raise StopIteration
__next__方法是返回迭代器中的下一个数据项。这一方法首先保存对当前数据项的引用,当循环变量增加一时,__next__方法便返回下一数据项,如果后面为空,则引发StopIteraton异常。(有点不太理解)
__iter__方法是负责创建并返回迭代器类的实例,在for循环中会自动调用。(不知它与这里的__init__在作用上有什么区别)
1.4.2 使用迭代器
在执行for循环命令时
>>> for item in bag:
... print item
python会自动调用__iter__方法,创建迭代器对象,此时迭代器状态如下图所示:
for循环会自动调用__next__方法来访问下一个数据项,迭代器随curItem增加而变化,直到遍历完所有数据项,引发StopIteration异常为止。for循环相当于如下过程:
# Create a BagIterator object for myBag.
iterator = myBag.__iter__()
# Repeat the while loop until break is called.
while True :
try:
# Get the next item from the bag. If there are no
# more items, the StopIteration exception is raised.
item = iterator.__next__()
# Perform the body of the for loop.
print item
# Catch the exception and break from the loop when we are done.
except StopIteration:
break