第六章 组合数据类型
组合数据类型概述
计算机不仅对单个变量表示的数据进行处理,更多情况,计算机需要对一组数据进行批量处理。一些例子包括:
- 给定一组单词{python, data, function, list, loop},计算并输出每个单词的长度;
- 给定一个学院学生信息,统计一下男女生比例;
- 一次实验产生了很多组数据,对这些大量数据进行分析;
组合数据类型能够将多个同类型或不同类型的数据组织起来,通过单一的表示使数据操作更有序更容易。根据数据之间的关系,组合数据类型可以分为三类:
序列类型、集合类型和映射类型。 - 序列类型是一个元素向量,元素之间存在先后关系,通过序号访问,元素之间不排他。
- 集合类型是一个元素集合,元素之间无序,相同元素在集合中唯一存在。
- 映射类型是“键-值”数据项的组合,每个元素是一个键值对,表示为(key, value)。
序列类型
序列类型是一维元素向量,元素之间存在先后关系,通过序号访问。
当需要访问序列中某特定值时,只需要通过下标标出即可。
序列类型支持成员关系操作符(in)、长度计算函数(len())、分片([]),元素本身也可以是序列类型。
Python语言中有很多数据类型都是序列类型,其中比较重要的是:str(字符串)、tuple(元组)和list(列表)。
- 元组是包含0个或多个数据项的不可变序列类型。元组生成后是固定的,其中任何数据项不能替换或删除。
- 列表则是一个可以修改数据项的序列类型,使用也最灵活
元组(tuple)是序列类型中比较特殊的类型,因为它一旦创建就不能被修改。元组类型在表达固定数据项、函数多返回值、多变量同步赋值、循环遍历等情况下十分有用。Python中元组采用逗号和圆括号(可选)来表示。
#需要打印所有变量(而不只是最后一个)
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"
creature = "cat", "dog", "tiger", "human"
creature
color = ("red", 0x001100, "blue", creature)
color
color[2]
color[-1][2]
('cat', 'dog', 'tiger', 'human')
('red', 4352, 'blue', ('cat', 'dog', 'tiger', 'human'))
'blue'
'tiger'
def func(x): #函数多返回值
return x, x**3
func(3)
a, b = 'dog', 'tiger' #多变量同步赋值
a, b = (b, a) #多变量同步赋值,括号可省略
print(a,b)
import math
for x, y in ((1,0), (2,5), (3,8)): #循环遍历
print(math.hypot(x,y)) #求多个坐标值到原点的距离
(3, 27)
tiger dog
1.0
5.385164807134504
8.54400374531753
集合类型
集合类型与数学中集合的概念一致,即包含0个或多个数据项的无序组合。集合中元素不可重复,元素类型只能是固定数据类型,例如:整数、浮点数、字符串、元组等,列表、字典和集合类型本身都是可变数据类型,不能作为集合的元素出现。
由于集合是无序组合,它没有索引和位置的概念,不能分片,集合中元素可以动态增加或删除。集合用大括号({})表示,可以用赋值语句生成一个集合。
S = {425, "BIT", (10, "CS"), 424}
S
T = {425, "BIT", (10, "CS"), 424, 425, "BIT"}
T
{424, 425, (10, 'CS'), 'BIT'}
{424, 425, (10, 'CS'), 'BIT'}
由于集合元素是无序的,集合的打印效果与定义顺序可以不一致。由于集合元素独一无二,使用集合类型能够过滤掉重复元素。set(x)函数可以用于生成集合。
W = set('apple')
W #自动按字母序排列
V = set(("cat", "dog", "tiger", "human"))
V
{'a', 'e', 'l', 'p'}
{'cat', 'dog', 'human', 'tiger'}
集合类型的4种基本操作,交集(&)、并集(|)、差集(-)、补集(^),操作逻辑与数学定义相同
集合类型主要用于三个场景:成员关系测试、元素去重和删除数据项
"BIT" in {"PYTHON", "BIT", 123, "GOOD"} #成员关系测试
tup = ("PYTHON", "BIT", 123, "GOOD", 123) #元素去重
set(tup)
newtup = tuple(set(tup)-{'PYTHON'}) # 去重同时删除数据项
newtup
True
{'PYTHON', 123, 'GOOD', 'BIT'}
(123, 'GOOD', 'BIT')
集合类型与其他类型最大的不同在于它不包含重复元素,因此,当需要对一维数据进行去重或进行数据重复处理时,一般通过集合来完成。
映射类型
映射类型是“键-值”数据项的组合,每个元素是一个键值对,即元素是(key, value),元素之间是无序的。键值对(key, value)是一种二元关系。在Python中,映射类型主要以字典(dict)体现。
dic = {'name':'张三','age':20}
dic
dic['name']
{'age': 20, 'name': '张三'}
'张三'
列表类型和操作
列表类型的概念
列表(list)是包含0个或多个对象引用的有序序列,属于序列类型。与元组不同,列表的长度和内容都是可变的,可自由对列表中数据项进行增加、删除或替换。列表没有长度限制,元素类型可以不同,使用非常灵活。
由于列表属于序列类型,所以列表也支持成员关系操作符(in)、长度计算函数(len())、分片([])。列表可以同时使用正向递增序号和反向递减序号,可以采用标准的比较操作符(<、<=、==、!=、>=、>)进行比较,列表的比较实际上是单个数据项的逐个比较。
列表用中括号([])表示,也可以通过list()函数将元组或字符串转化成列表。直接使用list()函数会返回一个空列表。
ls = [425, "BIT", [10, "CS"], 425]
ls
ls[2][-1][0]
'C'
list((425, "BIT", [10, "CS"], 425))
list("中国是一个伟大的国家")
list()
[425, 'BIT', [10, 'CS'], 425]
'C'
'C'
[425, 'BIT', [10, 'CS'], 425]
['中', '国', '是', '一', '个', '伟', '大', '的', '国', '家']
[]
与整数和字符串不同,列表要处理一组数据,因此,列表必须通过显式的数据赋值才能生成,简单将一个列表赋值给另一个列表不会生成新的列表对象。
ls = [425, "BIT", 1024] #用数据赋值产生列表ls
lt = ls #lt是ls所对应数据的引用,lt并不包含真实数据
ls[0] = 0
lt
[0, 'BIT', 1024]
列表类型的操作
vlist = list(range(5))
vlist
len(vlist[2:]) #计算从第3个位置开始到结尾的子串长度
2 in vlist #判断2是否在列表vlist中
vlist[3]="python" #修改序号3的元素值和类型
vlist
vlist[1:3]=["bit", "computer"]
vlist
[0, 1, 2, 3, 4]
3
True
[0, 1, 2, 'python', 4]
[0, 'bit', 'computer', 'python', 4]
与元组一样,列表可以通过for…in语句对其元素进行遍历,基本语法结构如下:
for <任意变量名> in <列表名>:
语句块
for e in vlist:
print(e, end=" ")
0 bit computer python 4
列表是一个十分灵活的数据结构,它具有处理任意长度、混合类型的能力,并提供了丰富的基础操作符和方法。当程序需要使用组合数据类型管理批量数据时,请尽量使用列表类型。
基本统计值计算
以最简单的统计问题为例,求解一组不定长数据的基本统计值,即平均值、标准差、中位数。
由于平均数、标准差和中位数是三个不同的计算目标,使用函数方式编写计算程序。
getNum()函数从用户输入获得数据
mean()函数计算平均值
dev()函数计算标准差
median()函数计算中位数
from math import sqrt
def getNum(): #获取用户输入
nums = []
iNumStr = input("请输入数字(直接输入回车退出): ")
while iNumStr != "":
nums.append(eval(iNumStr))
iNumStr = input("请输入数字(直接输入回车退出): ")
return nums
def mean(numbers): #计算平均值
s = 0.0
for num in numbers:
s = s + num
return s / len(numbers)
def dev(numbers, mean): #计算方差
sdev = 0.0
for num in numbers:
sdev = sdev + (num - mean)**2
return sqrt(sdev / (len(numbers)-1))
def median(numbers): #计算中位数
sorted(numbers)
size = len(numbers)
if size % 2 == 0:
med = (numbers[size//2-1] + numbers[size//2])/2
else:
med = numbers[size//2]
return med
n = getNum() #主体函数
m = mean(n)
print("平均值:{},方差:{:.2},中位数:{}.".format(m,dev(n,m),median(n)))
请输入数字(直接输入回车退出): 98
请输入数字(直接输入回车退出): 97
请输入数字(直接输入回车退出): 96
请输入数字(直接输入回车退出): 99
请输入数字(直接输入回车退出): 95
请输入数字(直接输入回车退出):
平均值:97.0,方差:1.6,中位数:96.
程序先后调用getNum()、mean()、dev()和median()函数。利用函数的模块化设计能够复用代码并增加代码的可读性。每个函数内部都采用了简单的语句。
列表在实现基本数据统计时发挥了重要作用,表现在:
- 列表是一个动态长度的数据结构,可以根据需求增加或减少元素;
- 列表的一系列方法或操作符为计算提供了简单的元素运算手段;
- 列表提供了对每个元素的简单访问方式及所有元素的遍历方式。
字典类型的计算
字典类型的基本概念
通过任意键信息查找一组数据中值信息的过程叫映射,Python语言中通过字典实现映射。Python语言中的字典可以通过大括号({})建立,建立模式如下:
{<键1>:<值1>, <键2>:<值2>, … , <键n>:<值n>}
其中,键和值通过冒号连接,不同键值对通过逗号隔开。
Dcountry={"中国":"北京", "美国":"华盛顿", "法国":"巴黎"}
print(Dcountry)
{'中国': '北京', '美国': '华盛顿', '法国': '巴黎'}
字典打印出来的顺序与创建之初的顺序不同,这不是错误。字典是集合类型的延续,各个元素并没有顺序之分。
字典最主要的用法是查找与特定键相对应的值,这通过索引符号来实现。
Dcountry["中国"]
'北京'
一般来说,字典中键值对的访问模式如下,采用中括号格式:
<值> = <字典变量>[<键>]
字典中对某个键值的修改可以通过中括号的访问和赋值实现:
Dcountry["中国"]='大北京'
Dcountry
{'中国': '大北京', '法国': '巴黎', '美国': '华盛顿'}
字典类型的操作
#通过中括号可以增加新的元素
Dcountry={"中国":"北京", "美国":"华盛顿", "法国":"巴黎"}
Dcountry["英国"]="伦敦"
print(Dcountry)
#直接使用大括号({})可以创建一个空的字典,并通过中括号([])向其增加元素
Dp={}
Dp['2^10']=1024
Dp
{'中国': '北京', '美国': '华盛顿', '法国': '巴黎', '英国': '伦敦'}
{'2^10': 1024}
字典类型的函数和方法举例:
Dcountry={"中国":"北京", "美国":"华盛顿", "法国":"巴黎"}
Dcountry.keys()
list(Dcountry.values())
Dcountry.items()
'中国' in Dcountry #只对键进行判断
Dcountry.get('美国', '悉尼') #'美国'在字典中存在
Dcountry.get('澳大利亚', '悉尼') #'澳大利亚'在字典中不存在
dict_keys(['中国', '美国', '法国'])
['北京', '华盛顿', '巴黎']
dict_items([('中国', '北京'), ('美国', '华盛顿'), ('法国', '巴黎')])
True
'华盛顿'
'悉尼'
与其他组合类型一样,字典可以通过for…in语句对其元素进行遍历,基本语法结构如下:
for <变量名> in <字典名>:
语句块
for key in Dcountry:
print(key)
中国
美国
法国
字典是实现键值对映射的数据结构,请理解如下基本原则:
- 字典是一个键值对的集合,该集合以键为索引,一个键信息只对应一个值信息;
- 字典中元素以键信息为索引访问;
- 字典长度是可变的,可以通过对键信息赋值实现增加或修改键值对。
jieba库的使用
jieba库的概述
jieba是Python中一个重要的第三方中文分词函数库
jieba库是第三方库,不是安装包自带,需要通过pip指令安装
pip install jieba # 或者 pip3 install jieba
import jieba
jieba.lcut("中国是一个伟大的国家")
Building prefix dict from the default dictionary ...
Dumping model to file cache F:\Temp\jieba.cache
Loading model cost 2.958 seconds.
Prefix dict has been built succesfully.
['中国', '是', '一个', '伟大', '的', '国家']
jieba库的解析
import jieba
jieba.lcut("中华人民共和国是一个伟大的国家")
jieba.lcut("中华人民共和国是一个伟大的国家", cut_all=True)
jieba.lcut_for_search("中华人民共和国是一个伟大的国家")
['中华人民共和国', '是', '一个', '伟大', '的', '国家']
['中华',
'中华人民',
'中华人民共和国',
'华人',
'人民',
'人民共和国',
'共和',
'共和国',
'国是',
'一个',
'伟大',
'的',
'国家']
['中华', '华人', '人民', '共和', '共和国', '中华人民共和国', '是', '一个', '伟大', '的', '国家']
文本词频统计
《Hamlet》英文词频统计
def getText():
txt = open("f:/code/python/Hamlet.txt", "r",encoding='utf8').read()
txt = txt.lower()
for ch in '!"#$%&()*+,-./:;<=>?@[\\]^_‘{|}~':
txt = txt.replace(ch," ") #将文本中特殊字符替换为空格
return txt
hamletTxt = getText()
words = hamletTxt.split()
counts = {}
for word in words:
counts[word] = counts.get(word,0) + 1
items = list(counts.items())
items.sort(key=lambda x:x[1], reverse=True)
for i in range(10):
word, count = items[i]
print ("{0:<10}{1:>5}".format(word, count))
the 1137
and 965
to 754
of 667
you 550
a 542
i 541
my 514
hamlet 461
in 436
观察输出结果可以看到,高频单词大多数是冠词、代词、连接词等语法型词汇,并不能代表文章的含义。进一步,可以采用集合类型构建一个排除词汇库excludes,在输出结果中排除这个词汇库中内容。
excludes = {"the","and","of","you","a","i","my","in","it","his","not","is","to","that","this","for","your","me","but","be","as","he","him","what"}
def getText():
txt = open("f:/code/python/Hamlet.txt", "r", encoding='utf8').read()
txt = txt.lower()
for ch in '!"#$%&()*+,-./:;<=>?@[\\]^_‘{|}~':
txt = txt.replace(ch, " ") #将文本中特殊字符替换为空格
return txt
hamletTxt = getText()
words = hamletTxt.split()
counts = {}
for word in words:
counts[word] = counts.get(word,0) + 1
for word in excludes:
del(counts[word])
items = list(counts.items())
items.sort(key=lambda x:x[1], reverse=True)
for i in range(10):
word, count = items[i]
print ("{0:<10}{1:>5}".format(word, count))
hamlet 461
lord 309
with 268
so 197
king 194
have 181
will 169
horatio 157
do 151
no 142
《三国演义》人物出场统计
import jieba
txt = open("f:/code/Python/三国演义.txt", "r", encoding='utf8').read()
words = jieba.lcut(txt)
counts = {}
for word in words:
if len(word) == 1: #排除单个字符的分词结果
continue
else:
counts[word] = counts.get(word,0) + 1
items = list(counts.items())
items.sort(key=lambda x:x[1], reverse=True)
for i in range(15):
word, count = items[i]
print ("{0:<10}{1:>5}".format(word, count))
曹操 936
孔明 831
将军 772
却说 656
玄德 570
关公 509
丞相 491
二人 466
不可 441
荆州 421
不能 387
孔明曰 385
玄德曰 383
如此 378
张飞 348
观察输出结果,同一个人物会有不同的名字,这种情况需要整合处理。同时,与英文词频统计类似,需要排除一些人名无关词汇,如“却说”、“将军”等。
import jieba
excludes = {"将军","却说","荆州","二人","不可","不能","如此"}
txt = open("f:/code/Python/三国演义.txt", "r", encoding='utf8').read()
words = jieba.lcut(txt)
counts = {}
for word in words:
if len(word) == 1:
continue
elif word == "诸葛亮" or word == "孔明曰":
rword = "孔明"
elif word == "关公" or word == "云长":
rword = "关羽"
elif word == "玄德" or word == "玄德曰":
rword = "刘备"
elif word == "孟德" or word == "丞相":
rword = "曹操"
else:
rword = word
counts[rword] = counts.get(rword,0) + 1
for word in excludes:
del(counts[word])
items = list(counts.items())
items.sort(key=lambda x:x[1], reverse=True)
for i in range(5):
word, count = items[i]
print ("{0:<10}{1:>5}".format(word, count))
曹操 1434
孔明 1373
刘备 1224
关羽 779
张飞 348
请继续完善程序,排除更多无关词汇干扰,总结出场最多的20个人物都有哪些。这里,给出参考答案。
曹操(1451)、孔明(1383)、刘备(1252)、关羽(784)、张飞(358)、
吕布(300)、赵云(278)、孙权(264)、司马懿(221)、周瑜(217)、
袁绍(191)、马超(185)、魏延(180)、黄忠(168)、姜维(151)、
马岱(127)、庞德(122)、孟获(122)、刘表(120)、夏侯惇(116)
Python之禅
什么样的程序是好的?如何编写漂亮的代码?这都是学习编程一段时间最经常提出的问题,却最难回答。程序设计语言如同自然语言,好的代码就像文学作品,不仅达意,更要优美。那什么是“好”?什么是“优美”?领悟编程代码优美的过程类似参禅,除了不断练习,也需要理解一些原则。
Python编译器以函数库的形式内置了一个有趣的文件,被称为“Python之禅”(The Zen of Python)。当调用如下一行语句后,会出现一段有趣的运行结果。
import this
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
Python之禅 作者:Tim Peters
优美胜于丑陋
明了胜于隐晦
简洁胜于复杂
复杂胜于凌乱
扁平胜于嵌套
间隔胜于紧凑
可读性很重要
即便假借特例的实用性之名,也不要违背上述规则
除非你确定需要,任何错误都应该有应对
当存在多种可能,不要尝试去猜测
只要你不是Guido,对于问题尽量找一种,最好是唯一明显的解决方案
做也许好过不做,但不假思索就动手还不如不做
如果你无法向人描述你的实现方案,那肯定不是一个好方案
如果实现方案容易解释,可能是个好方案
命名空间是绝妙的理念,要多运用
除了Python之禅所表达的Python设计理念,该程序还有另一段魅力:
s = """
Gur Mra bs Clguba, ol Gvz Crgref
Ornhgvshy vf orggre guna htyl.
Rkcyvpvg vf orggre guna vzcyvpvg.
Fvzcyr vf orggre guna pbzcyrk.
Pbzcyrk vf orggre guna pbzcyvpngrq.
Syng vf orggre guna arfgrq.
Fcnefr vf orggre guna qrafr.
Ernqnovyvgl pbhagf.
Fcrpvny pnfrf nera'g fcrpvny rabhtu gb oernx gur ehyrf.
Nygubhtu cenpgvpnyvgl orngf chevgl.
Reebef fubhyq arire cnff fvyragyl.
Hayrff rkcyvpvgyl fvyraprq.
Va gur snpr bs nzovthvgl, ershfr gur grzcgngvba gb thrff.
Gurer fubhyq or bar-- naq cersrenoyl bayl bar --boivbhf jnl gb qb vg.
Nygubhtu gung jnl znl abg or boivbhf ng svefg hayrff lbh'er Qhgpu.
Abj vf orggre guna arire.
Nygubhtu arire vf bsgra orggre guna *evtug* abj.
Vs gur vzcyrzragngvba vf uneq gb rkcynva, vg'f n onq vqrn.
Vs gur vzcyrzragngvba vf rnfl gb rkcynva, vg znl or n tbbq vqrn.
Anzrfcnprf ner bar ubaxvat terng vqrn -- yrg'f qb zber bs gubfr!
"""
d = {}
for c in (65, 97):
for i in range(26):
d[chr(i+c)] = chr((i+13) % 26 + c)
print("".join([d.get(c, c) for c in s]))
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
密文:A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
原文:N O P Q R S T U V W X Y Z A B C D E F G H I J K L M
密文:a b c d e f g h i j k l m n o p q r s t u v w x y z
原文:n o p q r s t u v w x y z a b c d e f g h i j k l m
这个算法可以看作是凯撒密码的一种扩展,相比凯撒密码,采用循环移动13个位置,加密和解密可以用同一个程序。
练习题