Python学习笔记¶

第六章 组合数据类型¶

从一个数据到一组数据。

集合、序列(元组、列表)、字典是最重要的三种结构。

6.1 集合类型及操作¶

6.1.1 集合类型定义¶

  • 多个元素的无序集合
  • 与数学中的集合概念一致
  • 无序、唯一、确定三个特性
  • 集合元素不可更改,不能是可变数据类型
  • 非可变数据类型:整数、浮点数、复数、字符串、元组
  • 集合用大括号表示,元素之间使用逗号分隔
  • 建立集合类型,使用{}或者set()
  • 建立空集合类型,必须使用set()
In [5]:
import os.path

A = {"python",123,("python",123.456)}
print(A)
B = set("hahaha123")
print(B)
C = {"python",123,"python",123,456}
print(C)
{123, ('python', 123.456), 'python'}
{'a', 'h', '3', '1', '2'}
{456, 123, 'python'}

6.1.2 集合操作符¶

一般是四种运算:交集 并集 补集 差集

  • S|T 并集 S和T中出现过的所有元素
  • S-T 差集 S中有但是T中没有的元素
  • S&T 交集 S和T中都有的元素
  • S^T 补集 包括S和T中非相同元素
  • S<=T S<T 判断子集关系,返回True/False
  • S>=T S>T 判断包含关系,返回True/False
  • S=|T 用S|T更新S
  • S-=T 用S-T更新S
  • S&=T 用S&T更新S
  • S^=T 用S^T更新S
In [8]:
A = {"y","p",123}
B = set("pypy123") # B = {'p','y','1','2','3'}
print(A-B)
print(B-A)
print(A&B)
print(A|B)
print(A^B)
{123}
{'3', '1', '2'}
{'y', 'p'}
{'y', 'p', '3', '1', 123, '2'}
{'1', '3', 123, '2'}

6.1.3 集合处理方法¶

  • S.add(x) 将x放入集合S
  • S.discard(x) 将x移出S,不在也没事
  • S.remove(x) 将x移出S,不在会报错KeyError
  • S.clear() 清除S所有元素🆑
  • S.pop() 随机返回S的一个元素,并删除这个元素,更新S,若S为空产生KeyError
  • S.copy() 返回S的一个副本
  • len(S) 返回元素个数
  • x in S 判断x元素是否属于集合S,返回True或者False
  • X not in S
  • set(x) 将其他类型变量转换为集合
In [12]:
A = {"p","y",123,456,'olg'}
for item in A:
    print(item,end=";")
olg;y;p;456;123;

由于没有顺序,所以这个输出顺序不一定是你定义时候的顺序

In [13]:
try:
    while True:
        print(A.pop(),end=";")
except:
    pass # 空语句

print(A)
olg;y;p;456;123;set()

A最后变成了一个空集合。

对于集合的赋值,仅传递引用。只有set()和花括号真正创建新的集合。

In [ ]:
set_olg = {"哼哼",114514}
set_olggg = set_olg
print(set_olggg)
set_olggg.add(1919810)
print(set_olg)
print(set_olggg)

6.1.4 集合类型应用场景¶

  • 包含关系的比较
    • 两组数据是否有包含关系
In [15]:
print("p" in {"p","y",123})
print({"p","y"} >= {"p","y",123})
True
False
  • 数据去重
    • 利用集合的特点删除掉所有重复的元素
    • 可以和列表相互转换,完成数据去重
In [18]:
ls = ["p","p","y","y",123]
print(ls)
A = set(ls)
print(A)
lt = list(A)
print(lt)
['p', 'p', 'y', 'y', 123]
{123, 'y', 'p'}
[123, 'y', 'p']

6.2 序列类型及操作¶

包括两种:元组和列表

6.2.1 序列类型定义¶

  • 序列是具有先后关系的一组元素
  • 序列是一维元素向量,元素类型可以不同
  • 元素内容可以重复
  • 类似于数学元素序列:s0,s1,...,sn-1
  • 元素间由序号引导,通过下标访问序列的特定元素
  • 序列类型是一个基类类型,一般不直接使用序列
    • 更常见使用衍生出来的类型
    • 字符串类型
    • 元组类型
    • 列表类型
  • 元素存在正向递增和反向递减的索引关系

image.png

6.2.2 序列处理函数及方法¶

  • x in s x是否在序列s中
  • x not in s x是否不在序列s中
  • s+t 连接两个序列
  • s*n n*s 将序列s重复n次
  • s[i] 索引
  • s[i:j:k] 切片
In [20]:
ls = ["python" , 123, ".io"]
print(ls[::-1])
s = "24岁,是学生"
print(s[::-1])
['.io', 123, 'python']
生学是,岁42
  • len(s) 返回序列s的长度
  • min(s) max(s) 返回最值,要求s中的元素可以比较
  • s.index(x) x第一次出现位置
  • s.index(x,i,j) 从i到j中x第一次出现位置
  • s.count(x) x在s中出现的次数
In [21]:
ls = ["python" , 123, ".io"]
print(len(ls))
print(max(ls)) # 不能比较会报错
3
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/var/folders/nc/rrdwnt790c13mtwryb6prjfc0000gn/T/ipykernel_68491/1349099150.py in <module>
      1 ls = ["python" , 123, ".io"]
      2 print(len(ls))
----> 3 print(max(ls))

TypeError: '>' not supported between instances of 'int' and 'str'
In [22]:
s="24岁,是学生"
print(max(s))
,

6.2.3 元组类型及操作¶

  • 本质是一个序列。
  • 但是特殊之处在于一旦被创建,就不能被修改。
  • 使用小括号()或者tuple()创建,元素用逗号分隔
  • 可以不使用小括号,也能创建一个元组
In [26]:
def fun():
    return 1,2,"24岁,是学生","哼哼哼啊啊啊啊啊啊啊啊"

print(fun())

creature = "cat","dog","human"
print(creature)
color = ("hehe",creature,114515,1919810,[114514,1919810])
print(color)
(1, 2, '24岁,是学生', '哼哼哼啊啊啊啊啊啊啊啊')
('cat', 'dog', 'human')
('hehe', ('cat', 'dog', 'human'), 114515, 1919810, [114514, 1919810])
  • 元组继承了所有序列的通用操作
  • 一旦定义,不能改变
  • 元组类型没有任何特殊操作
In [31]:
print(creature[::-1])
print(color[-4][-2])
('human', 'dog', 'cat')
dog
<class 'range'>

6.2.4 列表类型及操作¶

  • 本质是一种序列
  • 创建后可以随意修改
  • 使用方括号或者list()来创建,元素间使用逗号分隔
  • 元素内容可以不同,没有长度限制
  • 方括号和list()才创建真正的列表,赋值操作仅传递引用
In [37]:
ls = ["a","哼","aaaa",114514]
print(ls)
lt = ls
lt[2] = "bbbb"
print(lt)
print(ls)
['a', '哼', 'aaaa', 114514]
['a', '哼', 'bbbb', 114514]
['a', '哼', 'bbbb', 114514]

关于操作符:

  • ls[i] = x 替换列表ls的第i个元素为x
  • ls[i:j:k] = lt 用lt替换ls的切片内容,即切片内容不要了,全换成lt
  • del ls[i] 删除第i个元素
  • del ls[i:j:k] 删除i到j中以k为步长的元素
  • ls += lt 追加lt的内容到ls中
  • ls *= n 更新列表ls,使其元素重复n次
In [43]:
ls = ["a","哼","aaaa",114514]
ls[1:2] = [1,1,4,5,1,4]
print(ls)
del ls[::3]
print(ls)
ls*=2
print(ls)
['a', 1, 1, 4, 5, 1, 4, 'aaaa', 114514]
[1, 1, 5, 1, 'aaaa', 114514]
[1, 1, 5, 1, 'aaaa', 114514, 1, 1, 5, 1, 'aaaa', 114514]

关于方法:

  • ls.append(x) ls最后加一个x
  • ls.clear() 删除ls中所有元素
  • ls.copy() 生成新列表,复制ls中所有元素
  • ls.insert(i,x) 在第i位置增加元素x,即x元素会在i位置
  • ls.pop(i) 返回i位置元素并删除
  • ls.remove(x) 删除ls中第一次出现的x
  • ls.reverse() 元素反序
  • ls.sort(cmp=None, key=None, reverse=False) 对列表中的元素排序
    • cmp是指定的排序方法
    • key是指定用来排序的元素
    • reverse为True表示从大到小,降序
In [47]:
ls = ["a","哼","aaaa",114514]
ls.append(1234)
print(ls)
ls.insert(2,"1919810")
print(ls)
ls.reverse()
print(ls)
['a', '哼', 'aaaa', 114514, 1234]
['a', '哼', '1919810', 'aaaa', 114514, 1234]
[1234, 114514, 'aaaa', '1919810', '哼', 'a']
In [49]:
lt = list()
lt += [1,2,3,4,5]
lt[2]=114514
lt.insert(2,"olg")
del lt[1]
del lt[1:4]
0 in lt
lt.append(0)
lt.index(0)
len(lt)
max(lt)
lt.clear()

6.2.5 序列类型应用场景¶

  • 序列类型最重要的应用场景就是数据表示
  • 元组:表示元素不改变的应用场景,比如返回值
  • 列表更加灵活,最为常用
  • 序列类型最主要的作用还是数据表示,进而操作数据
  • 元素遍历,使用for in 即可
  • 元组可以实现一些数据保护,因为不可改变
In [50]:
ls = ["a","哼","aaaa",114514]
lt = tuple(ls)
print(lt)
('a', '哼', 'aaaa', 114514)

这样可以实现保护数据不被别人修改。

6.3 基本统计值计算¶

如果给定一组数据,想对它有个概要的理解,那么应该怎么做?

总个数、求和、平方、方差、中位数等统计值。

len() for ...in 等内容即可完成对应计算。

  • 提示:使用sorted()函数进行排序。
In [56]:
# 获取用户输入的数据,输入回车表示输入完毕
def getNum():
    nums = []
    user_input = input("请输入一个数字,按回车键退出")
    while user_input!="":
        nums.append(eval(user_input))
        user_input = input("请继续输入一个数字,按回车键退出")
    return nums

# 求和
def sum(nums):
    s = 0
    for i in nums:
        s += i
    return s

# 求平均值
def mean(nums):
    s = sum(nums)
    return s/len(nums)

# 求方差
def dev(nums,mean):
    sdev = 0
    for i in nums:
        sdev += (i-mean)**2
    return sdev/len(nums)

# 计算中位数
# 需要对列表进行排序
def median(nums):
    # 首先对于nums进行排序,使用sorted函数
    sorted(nums)
    # 之后判断个数是奇数还是偶数
    if len(nums)%2==0:
        # 偶数,中间两个数的平均值
        return (nums[len(nums)//2]+nums[len(nums)//2-1])/2
    else:
        # 中间这个数
        return nums[len(nums)//2]

n = getNum()
sum_num = sum(n)
mean_num = mean(n)
dev_num = dev(n,mean_num)
median_num = median(n)
print("求和{:.2f} , 平均数{:.2f} , 方差{:.2f} , 中位数{:.2f}".format(sum_num,mean_num,dev_num,median_num))
求和28.00 , 平均数3.50 , 方差5.25 , 中位数3.50

6.4 字典类型及操作¶

6.4.1 字典类型定义¶

  • 映射:一种键-值的对应,即索引和数据的对应
  • Key-Value
  • 序列类型使用非负整数进行索引,映射类型由用户为数据进行索引
  • 字典类型通过键值对建立这样一种映射
  • 字典是键值对的集合,键值对之间无序
  • 使用大括号{}或者dict()创建,键值对用冒号:表示
  • {键1:值1 , 键2:值2 , ...}
  • 可以通过[]来获得字典中的值
  • 字典变量[键] = [值]
  • [值] = 字典变量[键]
  • 字典变量[键] = [值]
  • 空大括号生成的是字典,所以集合不能用空大括号来生成
In [57]:
d = {"中国":"北京","美国":"圣地亚哥","法国":"巴黎"}
print(d)
print(d["中国"])
{'中国': '北京', '美国': '圣地亚哥', '法国': '巴黎'}
北京
In [59]:
de = {}
print(type(de))
de = dict()
print(type(de))
<class 'dict'>
<class 'dict'>

6.4.2 字典处理函数及方法¶

  • del d[k] 删除k对应的value
  • k in d k是否在这个字典d中
  • d.keys() 返回所有keys,注意不是列表,而是一种对象。但是可以通过list()函数转换为列表
  • d.values() 返回所有values,注意不是列表,而是一种对象。但是可以通过list()函数转换为列表
  • d.items() 返回所有key-values,注意不是列表,而是一种对象。但是可以通过list()函数转换为列表
In [91]:
d = {"中国":"北京","美国":"圣地亚哥","法国":"巴黎"}
print("中国" in d)
print(d.keys())
print(list(d.keys()))
print(d.values())
print(list(d.values()))
print(d.items())
print(list(d.items()))
True
dict_keys(['中国', '美国', '法国'])
['中国', '美国', '法国']
dict_values(['北京', '圣地亚哥', '巴黎'])
['北京', '圣地亚哥', '巴黎']
dict_items([('中国', '北京'), ('美国', '圣地亚哥'), ('法国', '巴黎')])
[('中国', '北京'), ('美国', '圣地亚哥'), ('法国', '巴黎')]

其他方法:

  • d.get(k,default) 如果k存在,返回value。如果不存在,返回default参数对应的值
  • d.pop(k,default) 如果k存在,返回value并删除这个key-value对。如果不存在,返回default参数对应的值
  • d.popitem() 随机取出并删掉一个键值对。以元组的形式返回
  • d.clear() 删除所有键值对
  • len(d) 返回字典d中元素的个数
In [96]:
d = {"中国":"北京","美国":"圣地亚哥","法国":"巴黎"}
print(d.get("中国","未找到"))
print(d.get("你好","未找到"))
print(d.popitem())
print(d)
北京
未找到
('法国', '巴黎')
{'中国': '北京', '美国': '圣地亚哥'}

可以使用list()或者set()或者tuple()完成对应操作,生成列表或者集合或者元组。

如果本身函数返回元组,那么就集合/列表/元组嵌套元组,转换非常灵活。

In [95]:
d = {"中国":"北京","美国":"圣地亚哥","法国":"巴黎"}
print(set(d))
print(set(d.keys()))
print(set(d.values()))
print(set(d.items()))
{'美国', '法国', '中国'}
{'美国', '法国', '中国'}
{'北京', '巴黎', '圣地亚哥'}
{('法国', '巴黎'), ('中国', '北京'), ('美国', '圣地亚哥')}
In [97]:
d = {"中国":"北京","美国":"圣地亚哥","法国":"巴黎"}
print(tuple(d))
print(tuple(d.keys()))
print(tuple(d.values()))
print(tuple(d.items()))
('中国', '美国', '法国')
('中国', '美国', '法国')
('北京', '圣地亚哥', '巴黎')
(('中国', '北京'), ('美国', '圣地亚哥'), ('法国', '巴黎'))
In [66]:
d = {}
d["中国"] = "北京"
d["美国"] = "圣地亚哥"
print(d)
d["美国"] = "华盛顿"
print(d)
print("美国" in d)
print(len(d))
d.clear()
print(d)

m = set()
print(m)
{'中国': '北京', '美国': '圣地亚哥'}
{'中国': '北京', '美国': '华盛顿'}
True
2
{}
set()

6.4.3 字典类型应用场景¶

  • 最常用:映射的表达
  • 映射无处不在
  • 最主要作用:表达键值对数据,进而操作它们
  • 例如,统计数据出现次数,每个数据是键,次数是值
  • 元素遍历 for k in d: 语句块

6.5 jieba库的使用¶

6.5.1 jieba库基本介绍¶

  • jieba是优秀的中文分词第三方库
  • 中文文本需要通过分词获得单个短语
  • jieba需要pip3额外安装
  • 提供了三种分词模式
  • 会一个函数就能用

image.png

  • jieba库依靠中文词库来完成分词
  • 确定汉字之间的关联概率
  • 如果概率比较大,那么就认为是一个单词

6.5.2 jieba库使用说明¶

  • 精确模式:把文本精确切分开,不存在冗余
  • 全模式:把所有可能的单词都扫描出来,有冗余
  • 搜索引擎模式:在精确模式的基础上,对长单词再次切分,更适用于搜索引擎对于短词语的搜索

常用函数:

  • jieba.lcut(s) 把s文本使用精确模式切分,以列表形式分词
  • jieba.lcut(s,cut_all=True) 按照全模式切分,得到列表,存在冗余
  • jieba.lcut_for_search(s) 按照搜索引擎模式分词
  • jieba.add_word(w) 向分词词典增加新的单词w
In [72]:
import jieba
s = "虽然刘海屏看起来不顺眼,但是对我来说,新款MacBook Pro确实很适合我,于是想着能不能先去线下店体验一下再进行下单。谁知,突然赶上了疫情,出去变得十分困难。同时了解到线下店只能买到最低配,而我的需求是32GB的内存和1TB左右的存储。线下店显然不能够满足我的需求,于是决定进行官网的购买。"
print(jieba.lcut(s))
print(jieba.lcut(s,cut_all=True))
print(jieba.lcut_for_search(s))
p = "中华人民共和国"
print(jieba._lcut_for_search(p))
['虽然', '刘海', '屏', '看起来', '不', '顺眼', ',', '但是', '对', '我', '来说', ',', '新款', 'MacBook', ' ', 'Pro', '确实', '很', '适合', '我', ',', '于是', '想着', '能', '不能', '先', '去', '线下', '店', '体验', '一下', '再', '进行', '下单', '。', '谁知', ',', '突然', '赶上', '了', '疫情', ',', '出去', '变得', '十分困难', '。', '同时', '了解', '到', '线下', '店', '只能', '买', '到', '最低', '配', ',', '而', '我', '的', '需求', '是', '32GB', '的', '内存', '和', '1TB', '左右', '的', '存储', '。', '线下', '店', '显然', '不', '能够', '满足', '我', '的', '需求', ',', '于是', '决定', '进行', '官网', '的', '购买', '。']
['虽然', '刘海', '屏', '看起', '看起来', '起来', '不顺', '顺眼', ',', '但是', '对', '我', '来说', ',', '新款', 'MacBook', '', ' ', '', 'Pro', '确实', '很', '适合', '我', ',', '于是', '想着', '能', '不能', '先去', '线下', '下店', '体验', '一下', '再', '进行', '下单', '。', '谁知', ',', '突然', '赶上', '了', '疫情', ',', '出去', '变得', '十分', '十分困难', '困难', '。', '同时', '了解', '到', '线下', '下店', '只能', '买到', '最低', '配', ',', '而', '我', '的', '需求', '是', '32GB', '的', '内存', '和', '1TB', '左右', '的', '存储', '。', '线下', '下店', '显然', '不能', '能够', '满足', '我', '的', '需求', ',', '于是', '决定', '进行', '官', '网', '的', '购买', '。']
['虽然', '刘海', '屏', '看起', '起来', '看起来', '不', '顺眼', ',', '但是', '对', '我', '来说', ',', '新款', 'MacBook', ' ', 'Pro', '确实', '很', '适合', '我', ',', '于是', '想着', '能', '不能', '先', '去', '线下', '店', '体验', '一下', '再', '进行', '下单', '。', '谁知', ',', '突然', '赶上', '了', '疫情', ',', '出去', '变得', '十分', '困难', '十分困难', '。', '同时', '了解', '到', '线下', '店', '只能', '买', '到', '最低', '配', ',', '而', '我', '的', '需求', '是', '32GB', '的', '内存', '和', '1TB', '左右', '的', '存储', '。', '线下', '店', '显然', '不', '能够', '满足', '我', '的', '需求', ',', '于是', '决定', '进行', '官网', '的', '购买', '。']
['中华', '华人', '人民', '共和', '共和国', '中华人民共和国']

6.6 实例:文本词频统计¶

中文和英文都进行考虑。有语言文本资料。

6.6.1 HAMLET英文文本词频统计¶

  • 还需要进行一些噪音处理、归一化等内容,并提取所有单词。
In [81]:
# 数据预处理
def getText():
    txt = open("/Users/caiguu/PycharmProjects/pythonLearn/hamlet.txt","r").read()
    txt = txt.lower() # 防止大小写造成干扰
    # 用空格消除掉特殊符号
    for ch in '|"$%&()*+,-./:;<=>?@[\\]^_{}!~':
        txt.replace(ch," ")
    return txt

hamlet = getText()
words = hamlet.split() # 用空格分隔,形成列表
# 使用字典来进行词频统计
counts = {}
for word in words:
    # 如果有,原数值+1
    # 如果没有,0+1
    # 然后赋值给counts[word]
    counts[word] = counts.get(word,0) + 1
# 转换为列表 每个键值对是一个元组,即列表中有好多元组
items = list(counts.items())
# 从大到小,按键值对第二个元素排序
# key指定排序方式,这里使用lambda函数,取得第二个元素,即值来代表整个键值对的值
# reverse为True表示从大到小排序
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         936
to          728
of          664
a           527
i           513
my          513
in          423
you         405
hamlet      401
In [80]:
# 拓展:使用lambda函数进行第二个元素完成排序
f = lambda y:y[1]
d= [(1,114514),(2,1919810),(3,114),(4,514),(5,24)]
d.sort(key=f)
print(d)
[(5, 24), (3, 114), (4, 514), (1, 114514), (2, 1919810)]

6.6.2 《三国演义》人物出场统计¶

  • 使用jieba库进行分词
  • 不用考虑标点符号和大小写
In [85]:
txt = open("/Users/caiguu/PycharmProjects/pythonLearn/threekingdoms.txt","r",encoding="utf-8").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=f,reverse=True)
for i in range(15):
    word , count = items[i]
    print("{0:<10}{1:>5}".format(word,count))
曹操          953
孔明          836
将军          772
却说          656
玄德          585
关公          510
丞相          491
二人          469
不可          440
荆州          425
玄德曰         390
孔明曰         390
不能          384
如此          378
张飞          358

现在已经完成了基本的单词词频分析,但是距离人物出场次数统计还差一点。下面改造这段代码,以满足需求。

问题:人物有不止一个的名称、孔明和孔明曰其实是一个人、二人这种词需要被排除掉

In [88]:
txt = open("/Users/caiguu/PycharmProjects/pythonLearn/threekingdoms.txt","r",encoding="utf-8").read()
# 构造一个排除词库
excludes = {"将军","二人","却说","不可","不能","如此","商议","如何","荆州","左右","引兵"}
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
# 使用del运算符,删除掉excludes中的内容
for ex in excludes:
    del counts[ex]
# 转换为列表,排序输出
items = list(counts.items())
items.sort(key=f,reverse=True)
for i in range(15):
    word , count = items[i]
    print("{0:<10}{1:>5}".format(word,count))
曹操         1451
孔明         1383
刘备         1252
关羽          784
张飞          358
主公          331
军士          317
吕布          300
军马          293
赵云          278
次日          271
大喜          268
孙权          264
天下          255
东吴          251

一直增加排除词库,最终能得到最终结果。

以后还可以接触到词云。