3.5 集合

集合(set)属于Python无序可变序列,使用一对大括号作为定界符,元素之间使用逗号分隔,同一个集合内的每个元素都是唯一的,不允许重复。另外,集合中只能包含数字、字符串、元组等不可变类型的数据,而不能包含列表、字典、集合等可变类型的数据。

3.5.1 集合对象的创建与删除

直接将集合赋值给变量即可创建一个集合对象。

     > > > a = {3, 5}                        #创建集合对象

也可以使用set()函数将列表、元组、字符串、range对象等其他可迭代对象转换为集合,如果原来的数据中存在重复元素,则在转换为集合的时候只保留一个;如果原序列或迭代对象中有不可散列的值,无法转换成为集合,抛出异常。

     > > > a_set = set(range(8, 14))         #把range对象转换为集合
     > > > a_set
    {8, 9, 10, 11, 12, 13}
     > > > b_set = set([0, 1, 2, 3, 0, 1, 2, 3, 7, 8])
                                            #转换时自动去掉重复元素
     > > > b_set
    {0, 1, 2, 3, 7, 8}
     > > > x = set()                         #空集合

当不再使用某个集合时,可以使用del命令删除整个集合。

3.5.2 集合操作与运算

(1)集合元素增加与删除

使用集合对象的add()方法可以增加新元素,如果该元素已存在则忽略该操作,不会抛出异常;update()方法用于合并另外一个集合中的元素到当前集合中,并自动去除重复元素。例如:

     > > > s = {1, 2, 3}
     > > > s.add(3)                      #添加元素,重复元素自动忽略
     > > > s
    {1, 2, 3}
     > > > s.update({3,4})               #更新当前字典,自动忽略重复的元素
     > > > s
    {1, 2, 3, 4}

集合对象的pop()方法用于随机删除并返回集合中的一个元素,如果集合为空则抛出异常;remove()方法用于删除集合中的元素,如果指定元素不存在则抛出异常;discard()用于从集合中删除一个特定元素,如果元素不在集合中则忽略该操作。

     > > > s.discard(5)                  #删除元素,不存在则忽略该操作
     > > > s
    {1, 2, 3, 4}
     > > > s.remove(5)                   #删除元素,不存在就抛出异常
    KeyError: 5
     > > > s.pop()                       #删除并返回一个元素
    1

(2)集合运算

内置函数len()、max()、min()、sum()、sorted()、map()、filter()、enumerate()等也适用于集合。另外,Python集合还支持数学意义上的交集、并集、差集等运算。例如:

     > > > a_set = set([8, 9, 10, 11, 12, 13])
     > > > b_set = {0, 1, 2, 3, 7, 8}
     > > > a_set | b_set                 #并集
    {0, 1, 2, 3, 7, 8, 9, 10, 11, 12, 13}
     > > > a_set & b_set             #交集
    {8}
     > > > a_set - b_set                 #差集
    {9, 10, 11, 12, 13}
     > > > a_set ^ b_set                 #对称差集
    {0, 1, 2, 3, 7, 9, 10, 11, 12, 13}

需要注意的是,关系运算符>、>=、<、<=作用于集合时表示集合之间的包含关系,而不是集合中元素的大小关系。例如,两个集合A和B,如果A<B不成立,不代表A>=B就一定成立。

     > > > {1, 2, 3} &lt; {1, 2, 3, 4}       #真子集
    True
     > > > {1, 2, 3} &lt;= {1, 2, 3}         #子集
    True

3.5.3 集合应用案例

例3-5 使用集合快速提取序列中的唯一元素。

问题描述:所谓唯一元素,这里是指不重复的元素。也就是说,如果原序列中某个元素出现多次,那么只保留一个。

基本思路:首先使用列表推导式生成一个包含100个10000以内随机数的列表,然后把列表转换为集合,自动去除重复元素。

     > > > import random
    #生成100个10000以内的随机数
     > > > listRandom = [random.choice(range(10000)) for i in range(100)]
     > > > newSet = set(listRandom)
     > > > print(newSet)

运行结果如下:

    {1538, 5130, 9746, 6162, …}       #略去更多结果

例3-6 获取指定范围内一定数量的不重复数字。

基本思路:主要利用集合元素不重复的特点,使用random模块中的randint()函数生成一个随机数,然后使用集合的add()方法将该随机数放入集合。如果集合中已经存在该数字则会自动忽略,如果不存在才会放入。

    1.  import random
    2.
    3.  def randomNumbers(number, start, end):
    4.      ''’使用集合来生成number个介于start和end之间的不重复随机数’''
    5.      data = set()
    6.      while len(data)&lt;number:
    7.         element = random.randint(start, end)
    8.         data.add(element)
    9.
    10.      return data
    11.  data = randomNumbers(10, 1, 100)
    12.  print(data)

某次运行结果如下:

    {67, 68, 35, 3, 71, 10, 81, 21, 24, 62}

当然,如果在项目中需要这样一个功能的时候,还是直接使用random模块的sample()函数更好一些。

     > > > import random
     > > > random.sample(range(1000), 20)    #在指定分布中选取不重复元素

运行结果如下:

    [61, 538, 873, 815, 708, 609, 995, 64, 7, 719, 922, 859, 807, 464, 789,651, 31, 702, 504, 25]

例3-7 测试指定列表中是否包含非法数据。

问题描述:这里所谓非法数据,是指不允许出现的数据。

基本思路:代码中假设lstColor是允许出现的合法数据,然后使用使用列表推导式生成一些随机数据,最后利用集合的差集运算来测试colors中是否只包含lstColor中的数据。

    1.  import random
    2.
    3.  lstColor = ('red', 'green', 'blue')
    4.  colors = [random.choice(lstColor) for i in range(10000)]
    5.
    6.  if set(colors)-set(lstColor):           #转换为集合之后再比较
    7.      print('error')

例3-8 电影评分与推荐。

问题描述:假设已有大量用户对若干电影的评分数据,现有某用户,也看过一些电影并进行过评分,要求根据已有打分数据为该用户进行推荐。

基本思路:用基于用户的协同过滤算法,也就是根据用户喜好来确定与当前用户最相似的用户,然后再根据最相似用户的喜好为当前用户进行推荐。本例采用字典来存储打分数据,格式为{用户1:{电影名称1:打分1, 电影名称2:打分2,…}, 用户2:{…}},首先在已有数据中查找与当前用户共同打分电影(使用集合的交集运算)数量最多的用户,如果有多个这样的用户就再从中选择打分最接近(打分的差距最小)的用户。代码中使用到了random模块中的randrange()函数,用来生成指定范围内的一个随机数。

    1.  from random import randrange
    2.
    3.  #历史电影打分数据,一共10个用户,每个用户对3~9个电影进行评分
    4.  #每个电影的评分最低1分、最高5分,这里是字典推导式和集合推导式的用法
    5.  data = {'user'+str(i):{'film'+str(randrange(1, 15)):randrange(1, 6)
    6.                          for j in range(randrange(3, 10))}
    7.         for i in range(10)}
    8.
    9.  #模拟当前用户打分数据,为5部随机电影打分
  10.  user = {'film'+str(randrange(1, 15)):randrange(1,6) for i in range(5)}
  11.  #最相似的用户及其对电影打分情况
  12.  #两个用户共同打分的电影最多
  13.  #并且所有电影打分差值的平方和最小
  14.  f = lambda item:(-len(item[1].keys()&amp;user),
  15.                   sum(((item[1].get(film)-user.get(film))**2
  16.                        for film in user.keys()&amp;item[1].keys())))
  17.  similarUser, films = min(data.items(), key=f)
  18.
  19.  #在输出结果中,第一列表示两个人共同打分的电影的数量
  20.  #第二列表示二人打分之间的相似度,数字越小表示越相似
  21.  #然后是该用户对电影的打分数据
  22.  print('known data'.center(50, '='))
  23.  for item in data.items():
  24.      print(len(item[1].keys()&amp;user.keys()),
  25.           sum(((item[1].get(film)-user.get(film))**2
  26.                for film in user.keys()&amp;item[1].keys())),
  27.           item,
  28.           sep=':')
  29.  print('current user'.center(50, '='))
  30.  print(user)
  31.  print('most similar user and his films'.center(50, '='))
  32.  print(similarUser, films, sep=':')
  33.  print('recommended film'.center(50, '='))
  34.  #在当前用户没看过的电影中选择打分最高的进行推荐
  35.  print(max(films.keys()-user.keys(), key=lambda film: films[film]))

某次运行结果如图3-3所示,在所有已知用户中,user7和user9都与当前用户共同打分的电影数量最多,都是3。但是,user7与当前用户打分的距离是9,而user9的距离是20,所以user7与当前用户更接近一些,最终选择该用户进行推荐。

图3-3 运行结果