集合的特点与数据要求

  1. 不允许出现重复元素
  2. 元素排列无特定顺序
  3. 只能包含不可变类型
  1. a=['东风', '东风', '幺鸡', '红中', '发财', '白板', '东风']
  2. a = set(a)
  3. a
  1. {'东风', '发财', '幺鸡', '白板', '红中'}
  1. a = list(a)
  2. a
  1. ['白板', '发财', '东风', '幺鸡', '红中']
  1. a = {1, 'xyz', (25, 37)}
  1. a = {1, 'xyz', (25, 37), [2, 3]}
  1. ---------------------------------------------------------------------------
  2. TypeError Traceback (most recent call last)
  3. <ipython-input-5-321b2a8ee442> in <module>
  4. ----> 1 a = {1, 'xyz', (25, 37), [2, 3]}
  5. TypeError: unhashable type: 'list'

列表属于可变类型的数据,不能作为集合的元素

可变对象
列表集合字典等类型的数据,其在内存中的内容可以随时修改、扩充

  1. a = [['中国', 'CHN'], ['美国', 'USA'], ['英国', 'UK']]
  2. s = set(a)
  1. ---------------------------------------------------------------------------
  2. TypeError Traceback (most recent call last)
  3. <ipython-input-7-b30da0cf6bfa> in <module>
  4. 1 a = [['中国', 'CHN'], ['美国', 'USA'], ['英国', 'UK']]
  5. ----> 2 s = set(a)
  6. TypeError: unhashable type: 'list'

假如这个转换能够成功,得到的集合就是:
{[‘中国’, ‘CHN’], [‘美国’, ‘USA’], [‘英国’, ‘UK’]}
然而列表属于可变类型的数据,不能作为集合的元素

set类型与集合运算

交集、并集、差集

交集运算符:&

空字典:{}
空集合:set()
判断变量a是否为空集合:

  1. if a == set():
  2. if len(a) == 0:

  1. if a:
  2. print('a不是空集合')
  3. else:
  4. print('a是空集合')
  1. a = {'A', 'B', 'C', 'D'}
  2. b = {'B', 'E', 'D', 'F'}
  3. c = a & b
  4. c
  1. {'B', 'D'}

并集运算符:|

  1. a = {'A', 'B', 'C', 'D'}
  2. b = {'B', 'E', 'D', 'F'}
  3. c = a | b
  4. c
  1. {'A', 'B', 'C', 'D', 'E', 'F'}

差集运算符:-

  1. a = {'A', 'B', 'C', 'D'}
  2. b = {'B', 'E', 'D', 'F'}
  3. c = a - b
  4. c
  1. {'A', 'C'}

image.png

  1. 射雕 = {'黄药师', '欧阳锋', '段智兴', '洪七公', '王重阳'}
  2. 神雕 = {'黄药师', '杨过', '段智兴', '郭靖', '周伯通'}
  1. 射雕 - 神雕 # 差集
  1. {'欧阳锋', '洪七公', '王重阳'}
  1. 神雕 - 射雕 # 差集
  1. {'周伯通', '杨过', '郭靖'}
  1. 射雕 & 神雕 # 交集
  1. {'段智兴', '黄药师'}
  1. 射雕 | 神雕 # 并集
  1. {'周伯通', '杨过', '欧阳锋', '段智兴', '洪七公', '王重阳', '郭靖', '黄药师'}

集合对象的方法

集合对象的各种方法可以方便实现添加、修改、删除、判断等操作。

  1. a = {2, 3, 5}
  2. b = {1, 2, 3, 4, 5, 6, 7}
  1. a.issubset(b)
  1. True
  1. b.issuperset(a)
  1. True
方法或运算符 作用 示例
add 向集合内添加元素 s.add(x)
remove 从集合内删除指定数值的元素 s.remove(x)
pop 让集合删除并返回一个元素
(由 Python自己决定删除哪个
元素,程序员不能指定)
x = s.pop()
clear 从集合内移除所有元素
issubset 判断本集合是否为另一个集合
的子集,是则返回True
a.issubset(b)
<= 同 issubset a <= b
issuperset 判断本集合是否为另一个集合
的父集,是则返回True
a.issuperset(b)
>= 同 issuperset a >= b
union 返回本集合与其他集合的并集 a.union(b, c)
intersection 返回本集合与其他集合的交集
difference 返回本集合与其他集合的差集
isdisjoint 判断本集合与其他集合的交集是否为空(即不相交)

在岗员工统计

  1. import xlwings as xw
  2. app = xw.App()
  3. wb = app.books.open('员工名单.xlsx')
  4. ws = wb.sheets[0]
  5. n1 = ws.range('A4:A16901').value
  6. n2 = ws.range('B4:B16890').value
  7. # 离职员工
  8. ws.range('E2').value = list(set(n1) - set(n2))
  9. # 新员工
  10. ws.range('F2').value = list(set(n2) - set(n1))
  11. # 老员工
  12. ws.range('G2').value = list(set(n1) & set(n2))
  13. wb.save('员工名单.xlsx')
  14. wb.close()
  15. app.quit()

【注意】
在xlwings中,将一维列表写入range,默认情况下是横向书写,即写到一行里。
如果要将一维列表纵向写入一列,应使用以下写法:

  1. range().options(transpose=True).value = list(一维列表)

指定本次写入后用“转置”选项

使用 collections. Counter(计数器)实现频次统计

collections模块常用工具:
有序字典 OrderedDict,既能像字典样存放键值对,又能像列表一样按下标引用
双向列表 deque,类似列表,但是插入删除非常快
计数器 Counter,用法:Counter(可迭代对象),返回特殊的字典类型,用法与字典完全相同,额外支持加法、减法、交集、并集等运算

  1. from collections import Counter
  2. a = ['张三', '李四', '张三', '王五', '张三', '李四', '张三', '王五', '王五', '张三', '赵六', '赵六']
  3. c = Counter(a)
  4. c
  1. Counter({'张三': 5, '李四': 2, '王五': 3, '赵六': 2})
  1. c.values()
  1. dict_values([5, 2, 3, 2])
  1. day1 = ['李四', '张三', '李四', '李四', '王五', '王五', '田七', '张三', '刘八', '田七', '李四']
  2. day2 = ['张三', '李四', '张三', '王五', '张三', '李四', '张三', '王五', '王五', '张三', '赵六', '赵六']
  1. c1 = Counter(day1)
  2. c1
  1. Counter({'李四': 4, '张三': 2, '王五': 2, '田七': 2, '刘八': 1})
  1. c2 = Counter(day2)
  2. c2
  1. Counter({'张三': 5, '李四': 2, '王五': 3, '赵六': 2})
  1. c1 + c2
  1. Counter({'李四': 6, '张三': 7, '王五': 5, '田七': 2, '刘八': 1, '赵六': 2})

Counter对象之间可以进行加减运算,按照“同类合并”的方式对各个键的频次进行加减。不过在减法运算中,如果某个元素相减后为负数或零,则不出现在结果集合中。此外Counter对象之间还可以进行交集和并集运算。

二维可迭代对象扁平化

应用场景:
max函数处理二维列表时,并不去查找最大元素,而是比较每个子列表的第一项返回第一项最大的子列表。
如果第一项都相同,则顺次比较第二项,如此类推……
如何使用max函数求取二维列表中的最大值?

  1. a = [[1, 5], [3, 4], [2, 1, 3]]
  2. max(a)
  1. [3, 4]

列表生成式

  1. a = [[1, 5], [3, 4], [2, 1, 3]]
  2. b = [max(x) for x in a]
  3. c = max(b)
  4. c
  1. 5

使用 itertools.chain,将二维列表合并为一维列表

itertools:处理可迭代对象的工具
chain:串联参数

  1. from itertools import chain
  2. a = [1, 3, 5]
  3. b = [2, 4, 6]
  4. c = [3, 6, 9]
  5. x = chain(a, b, c)
  6. x
  7. # chain函数返回的不是列表,而是Chain类型的可迭代对象
  1. <itertools.chain at 0x1ceac581278>
  1. x = list(x)
  2. x
  1. [1, 3, 5, 2, 4, 6, 3, 6, 9]
  1. a = [[1, 5], [3, 4], [2, 1, 3]]
  2. x = list(chain(a))
  3. x
  1. [[1, 5], [3, 4], [2, 1, 3]]

解包

  1. x = list(chain(*a)) # chain(*a)相当于chain(a[0], a[1], a[2])
  2. x
  1. [1, 5, 3, 4, 2, 1, 3]
  1. a = [[1, 5], [3, [4, 5, 6]], [2, [2, 3, [3, 5, 1]]]]
  2. x = list(chain(*a))
  3. x
  1. [1, 5, 3, [4, 5, 6], 2, [2, 3, [3, 5, 1]]]

对于维度复杂的容器,不能使用chain方法,需要自己编写递归程序、或使用第三方模块。

练习

练习1:集合计算考勤名单

请先下载 某企业考勤记录文件 。该文件的“考勤统计”工作表中,B、C、D 三列分别记录了三周的出勤记录。因为每周有5天,所以一个员工在一周内可能出勤多次。

请编写程序,使用print输出以下统计结果:

第一周出过勤,但第二周完全缺勤的人员名单(即第二周从未出现过该名字)

第二周出过勤,但第一周完全缺勤的人员名单

从第一周到第三周全都出过勤、从未缺勤的人员名单

第一周和第二周全都出过勤、但第三周完全缺勤的人员名单

从第一周到第三周,所有曾经出勤(即使只在一周出过勤)的人员总名单

完成之后,请改写程序,将结果直接写入Excel工作表(自行指定行列位置)。

  1. import xlwings as xw
  2. app = xw.App()
  3. wb = app.books.open('python02_19_01.xlsx')
  4. ws = wb.sheets[0]
  5. w1 = ws.range('B3:B30').value
  6. w2 = ws.range('C3:C30').value
  7. w3 = ws.range('D3:D30').value
  8. at1ab2 = list(set(w1) - set(w2))
  9. at2ab1 = list(set(w2) - set(w1))
  10. at123 = list(set(w1) & set(w2) & set(w3))
  11. at12ab3 = list((set(w1) | set(w2)) - set(w3))
  12. atonce = list(set(w1) | set(w2) | set(w3))
  13. ws.range('E2').options(transpose = True).value = at1ab2
  14. ws.range('F2').options(transpose = True).value = at2ab1
  15. ws.range('G2').options(transpose = True).value = at123
  16. ws.range('H2').options(transpose = True).value = at12ab3
  17. ws.range('I2').options(transpose = True).value = atonce
  18. wb.save('python02_19_01_统计.xlsx')
  19. wb.close()
  20. app.quit()
  1. at1ab2
  1. ['王五', '崔九']
  1. at2ab1
  1. ['赵六']
  1. at123
  1. ['李四', '刘八', '黄大', '田七', '王二', '顾十']
  1. at12ab3
  1. ['张三']
  1. atonce
  1. ['李四', '崔九', '王五', '黄大', None, '田七', '顾十', '刘八', '赵六', '王二', '张三']
  1. 用 xlwings 将 B3:B30 、C3:C30 和 D3:D30 读入三个列表;
  2. 用 set() 将三个列表转换为三个集合,比如 s1、s2、s3 ;
  3. 问题1 即 s1-s2 ,问题2 即 s2-s1 ,问题3 即 s1&s2&s3 ,问题4 即 s1&s2-s3 ,问题5 即 s1|s2|s3 。

作业2:Counter统计出勤次数

仍然使用上一作业中已经下载过的 某企业考勤记录文件 。请使用Counter完成以下统计工作,并将结果print到屏幕上:

统计第一周每个人的出勤次数,以字典形式打印在屏幕上;

统计刘八在第一周、第二周和第三周分别出勤几次,把三个数字打印在屏幕上;

统计每个人在三周里的总出勤次数,即每周出勤次数之和;

统计第三周比第二周出勤更多的人员名单,比如刘八在第三周出勤5次,而在第二周只出勤2次,所以应当显示在屏幕上。

  1. from collections import Counter
  2. count1 = Counter(w1)
  3. countliu1 = count1['刘八']
  4. count2 = Counter(w2)
  5. countliu2 = count2['刘八']
  6. count3 = Counter(w3)
  7. countliu3 = count3['刘八']
  8. count_all = count1 + count2 + count3
  9. count3_more = count3 - count2
  1. print(count1)
  1. Counter({'田七': 5, '崔九': 4, '王二': 3, '张三': 3, '王五': 3, '黄大': 3, '李四': 3, '刘八': 2, '顾十': 2})
  1. print(countliu1)
  1. 2
  1. print(countliu2)
  1. 2
  1. print(countliu3)
  1. 4
  1. print(count_all)
  1. Counter({'田七': 12, '王二': 9, '黄大': 9, '李四': 9, '刘八': 8, '崔九': 8, '顾十': 8, '赵六': 8, '张三': 7, '王五': 5, None: 1})
  1. print(count3_more)
  1. Counter({'崔九': 4, '顾十': 2, '刘八': 2, '王五': 2, None: 1})

本练习的目的是掌握Counter类型基本用法,以及Counter对象之间的加减法。具体思路如下:

用 xlwings 将 B3:B30 、C3:C30 和 D3:D30 读入三个列表,比如 s1 、s2 和 s3 ;

用 Counter(s1) 即可完成问题1的统计要求;

如果让变量 d=Counter(s1),那么 d[‘刘八’] 就是刘八在第一周的出勤次数,或者可以直接写为 Counter(s1)[‘刘八’] 。其他各周以此类推。

总出席次数其实就是 Counter(s1)+Counter(s2)+Counter(s3) 。

使用 Counter(s3)-Counter(s2) ,就可以得到第三周与第二周每个人的出勤次数之差,而且只保留正数结果(即第三周出勤更多的人)。

以上每个任务的最终结果(一个Counter类型的特殊字典对象),都可以用 for k:v in d.items() 的形式逐个扫描其姓名和次数,从而逐个打印在屏幕上或写入到Excel中。

练习3:chain实现扁平化

仍然使用上一作业中已经下载过的 某企业考勤记录文件 。请思考并试验:

如果使用 xlwings 一次性读入 range(‘B3:D30’),那么得到的就是一个二维列表。接下来能否直接从这个二维列表中得到每个人的三周总出勤次数(即第一周、第二周和第三周出勤次数之和)?

  1. import xlwings as xw
  2. app = xw.App()
  3. wb = app.books.open('python02_19_01.xlsx')
  4. ws = wb.sheets[0]
  5. data = ws.range('B3:D30').value
  6. from itertools import chain
  7. data = list(chain(*data))
  8. count_all = Counter(data)
  9. print(count_all)
  1. Counter({'田七': 12, '王二': 9, '黄大': 9, '李四': 9, '赵六': 8, '刘八': 8, '崔九': 8, '顾十': 8, '张三': 7, '王五': 5, None: 1})