Python中集合set和字典dict的用法区别

核心知识点

1.set是一个无序不重复的序列

2.可以用 { } 或者 set( ) 函数创建集合

3.集合存放不可变类型(字符串、数字、元组)

注意:创建一个空集合必须用 set( ) 而不是 { } ,因为 { } 是用来创建一个空字典

Python列表(list)、元组(tuple)、字典(dict)和集合(set)详解

Python 序列(Sequence): 是指按特定顺序依次排列的一组数据,它们可以占用一块连续的内存,也可以分散到多块内存中。Python 中的序列类型包括列表(list)元组(tuple)字典(dict)集合(set)

数据是有序列表(list)元组(tuple)比较相似,它们都按顺序保存元素,所有的元素占用一块连续的内存,每个元素都有自己的索引,因此列表和元组的元素都可以通过索引(index)来访问。它们的区别在于:列表是可以修改的,而元组是不可修改的。

数据是无序字典(dict)集合(set)存储的数据都是无序的,每份元素占用不同的内存,其中字典元素以 【key-value 】的形式保存。

Python set集合详解

Python 中的集合,和数学中的集合概念一样,用来保存不重复的元素,即集合中的元素都是唯一的,互不相同。

从形式上看,和字典类似,Python 集合会将所有元素放在一对大括号 {} 中,相邻元素之间用“,”分隔,如下所示:

{element1,element2,...,elementn}

其中,elementn 表示集合中的元素,个数没有限制。

从内容上看,同一集合中只能存储不可变的数据类型,包括整形、浮点型、字符串、元组, 无法存储列表、字典、集合这些可变的数据类型,否则 Python 解释器会抛出 TypeError 错误。比如说:

>>> {{'a':1}}
Traceback (most recent call last):
  File "<pyshell#8>", line 1, in <module>
    {{'a':1}}
TypeError: unhashable type: 'dict'
>>> {[1,2,3]}
Traceback (most recent call last):
  File "<pyshell#9>", line 1, in <module>
    {[1,2,3]}
TypeError: unhashable type: 'list'
>>> {{1,2,3}}
Traceback (most recent call last):
  File "<pyshell#10>", line 1, in <module>
    {{1,2,3}}
TypeError: unhashable type: 'set'

并且需要注意的是,数据必须保证是唯一的,因为集合对于每种数据元素,只会保留一份。 例如:

>>> {1,2,1,(1,2,3),'c','c'}
{1, 2, 'c', (1, 2, 3)}

由于 Python 中的 set 集合是无序的,所以每次输出时元素的排序顺序可能都不相同。

其实,Python 中有两种集合类型,一种是 set 类型的集合,另一种是 frozenset 类型的集合,它们唯一的区别是,set 类型集合可以做添加、删除元素的操作,而 forzenset 类型集合不行。本节先介绍 set 类型集合,后续章节再介绍 forzenset 类型集合。

1. Python创建set集合

Python创建set集合

Python 提供了 2 种创建 set 集合的方法,分别是使用 {} 创建和使用 set() 函数将列表、元组等类型数据转换为集合。
1. 使用 {} 创建:

在 Python 中,创建 set 集合可以像列表、元素和字典一样,直接将集合赋值给变量,从而实现创建集合的目的,其语法格式如下:

setname = {element1,element2,...,elementn}

其中,setname 表示集合的名称,起名时既要符合 Python 命名规范,也要避免与 Python 内置函数重名。

举个例子:

a = {1,'c',1,(1,2,3),'c'}
print(a)
>>>
{1, 'c', (1, 2, 3)}

2. set()函数创建集合:

set() 函数为 Python 的内置函数,其功能是将字符串、列表、元组、range 对象等可迭代对象转换成集合。该函数的语法格式如下:

setname = set(iteration)

其中,iteration 就表示字符串、列表、元组、range 对象等数据。

例如:

set1 = set("c.biancheng.net")
set2 = set([1,2,3,4,5])
set3 = set((1,2,3,4,5))
print("set1:",set1)
print("set2:",set2)
print("set3:",set3)
>>>
set1: {'a', 'g', 'b', 'c', 'n', 'h', '.', 't', 'i', 'e'}
set2: {1, 2, 3, 4, 5}
set3: {1, 2, 3, 4, 5}

注意,如果要创建空集合,只能使用 set() 函数实现。因为直接使用一对 {},Python 解释器会将其视为一个空字典。

2. Python访问set集合元素

由于集合中的元素是无序的,因此无法向列表那样使用下标访问元素。Python 中,访问集合元素最常用的方法是使用循环结构,将集合中的数据逐一读取出来

a = {1,'c',1,(1,2,3),'c'}
for ele in a:
    print(ele,end=' ')
>>>
1 c (1, 2, 3)

3. Python删除set集合

和其他序列类型一样,手动函数集合类型,也可以 使用 del() 语句,例如:

a = {1,'c',1,(1,2,3),'c'}
print(a)
del(a)
print(a)
>>>
{1, 'c', (1, 2, 3)}
Traceback (most recent call last):
  File "C:\Users\mengma\Desktop\1.py", line 4, in <module>
    print(a)
NameError: name 'a' is not defined

Python set集合基本操作(添加、删除、交集、并集、差集)

向 set 集合中添加元素

set 集合中添加元素,可以使用 set 类型提供的 add() 方法实现,该方法的语法格式为:

setname.add(element)

其中,setname 表示要添加元素的集合,element 表示要添加的元素内容。

需要注意的是,使用 add() 方法添加的元素,只能是数字字符串元组或者布尔类型(True 和 False)值,不能添加列表、字典、集合这类可变的数据,否则 Python 解释器会报 TypeError 错误。例如:

a = {1,2,3}
a.add((1,2))
print(a)
a.add([1,2])
print(a)
>>>
{(1, 2), 1, 2, 3}
Traceback (most recent call last):
  File "C:\Users\mengma\Desktop\1.py", line 4, in <module>
    a.add([1,2])
TypeError: unhashable type: 'list'

从set集合中删除元素

删除现有 set 集合中的指定元素,可以使用 remove() 方法,该方法的语法格式如下:

setname.remove(element)

使用此方法删除集合中元素,需要注意的是,如果被删除元素本就不包含在集合中,则此方法会抛出 KeyError 错误,例如:

a = {1,2,3}
a.remove(1)
print(a)
a.remove(1)
print(a)
>>>
{2, 3}
Traceback (most recent call last):
  File "C:\Users\mengma\Desktop\1.py", line 4, in <module>
    a.remove(1)
KeyError: 1

上面程序中,由于集合中的元素 1 已被删除,因此当再次尝试使用 remove() 方法删除时,会引发 KeyError 错误。

如果我们不想在删除失败时令解释器提示 KeyError 错误,还可以使用 discard() 方法,此方法和 remove() 方法的用法完全相同,唯一的区别就是,当删除集合中元素失败时,此方法不会抛出任何错误

a = {1,2,3}
a.remove(1)
print(a)
a.discard(1)
print(a)
>>>
{2, 3}
{2, 3}

Python set集合做交集、并集、差集运算

集合最常做的操作就是进行交集、并集、差集以及对称差集运算,首先有必要给大家普及一下各个运算的含义。

在这里插入图片描述

图 1 集合示意图

图 1 中,有 2 个集合,分别为 set1={1,2,3} 和 set2={3,4,5},它们既有相同的元素,也有不同的元素。以这两个集合为例,分别做不同运算的结果如表 1 所示。

表 1 Python set集合运算

运算操作Python运算符含义例子输出结果
交集&取两集合公共的元素>>> set1 & set2{3}
并集|取两集合全部的元素>>> set1 | set2{1,2,3,4,5}
差集-取一个集合中另一集合没有的元素>>> set1 - set2{1,2}
对称差集^取集合 A 和 B 中不属于 A&B 的元素>>> set1 ^ set2{1,2,4,5}

Python set集合方法详解(全)

前面学习了 set 集合,本节来一一学习 set 类型提供的方法。首先,通过 dir(set) 命令可以查看它有哪些方法:

>>> dir(set)
['add', 'clear', 'copy', 'difference', 'difference_update', 'discard', 'intersection', 'intersection_update', 'isdisjoint', 'issubset', 'issuperset', 'pop', 'remove', 'symmetric_difference', 'symmetric_difference_update', 'union', 'update']

各个方法的具体语法结构及功能如表 1 所示。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
原文参考链接:Python set集合方法详解(全)

set.update(x) 可更新多个值

update(x),将x添加到集合中,且参数可以是列表、元组、字典等。

备注:添加字典只能添加不可变的–

# update(x),将x添加到集合中,且参数可以是列表、元组、字典等
s = set(('a', 'cc', 'f'))
# 添加字典只能添加不可变的--键
dict_1 = {'name': 'bb', 'age': 'cc', 'f': 11}
s.update(dict_1)
print("添加字典"+str(s))
>>>
添加字典{'name', 'a', 'age', 'f', 'cc'}

示例2:

s = set(('a', 'cc', 'f'))
tup_1 = (1, 2,)
s.update(tup_1)
print(s)
>>>
{1, 2, 'a', 'f', 'cc'}


s = set(('a', 'cc', 'f'))
list_1 = ['w', 'a', 1]
s.update(list_1)
print(s)
>>>
{1, 'f', 'w', 'a', 'cc'}

set.add(x)将元素x添加到集合里

set.add(x),将元素x添加到集合里。

# add(x)将元素x添加到集合里
s = {1,2,3,4,5,}
s.add('5')
print(s)
>>>
{1, 2, 3, 4, 5, '5'}

set.remove(x)移除集合中元素,如果移除的元素不在集合中将发生错误

set.remove(x)移除集合中元素,如果移除的元素不在集合中将发生错误

# 移除集合中元素,如果移除的元素不在集合中将发生错误
s = set(('a', 'cc', 'f'))
s.remove('cc')
print(s)
{'a', 'f'}

示例2:

s = set(('a', 'cc', 'f'))
s.remove('mm')
print(s)
>>>
Traceback (most recent call last):
  File "C:/Users/123/PycharmProjects/py2018/test", line 104, in <module>
    s.remove('mm')
KeyError: 'mm'

set.pop()随机删除集合中元素

# 随机删除集合中元素
s = set(('a', 'cc', 'f'))
s.pop()
print(s)
{'a', 'cc'}

set.clear()清空集合

# 清空集合
s = set(('a', 'cc', 'f'))
s.clear()
print(s)
>>>
set()

len(set)计算set元素个数

计算set元素个数
s = set(('a', 'cc', 'f'))
print('集合元素个数为:'+str(len(s)))
集合长度为:3

set.copy复制集合

set.copy
s = set(('a', 'cc', 'f'))
s1 = s.copy()
print(s1)
{'a', 'f', 'cc'}

差集set1.difference(set2)

差集(-)  (difference)
复制代码
# difference求差集 或者用 -
s = set(('a', 'cc', 'f'))
s1 = {'a', 'f', 1, 'ww'}
# 两种求差集的方法
print("在s中不在s1中: "+str(s.difference(s1)))
print('在s1中不在s中: '+str(s1-s))
复制代码
在s中不在s1中: {'cc'}
在s1中不在s中: {'ww', 1}

交集set1.intersection(set2)

交集(&)  (intersection)
s = set(('a', 'cc', 'f'))
s1 = {'a', 'f', 1, 'ww'}
# 同时在集合s 和 s1 中的元素
print(s.intersection(s1))
print(s1&s)
{'a', 'f'}
{'a', 'f'}

并集set1.union(set2)

并集(|)  (union)
s = set(('a', 'cc', 'f'))
s1 = {'a', 'f', 1, 'ww'}
# 元素在集合 s 中或在集合 s1 中
print(s.union(s1))
print(s1|s)
{'a', 1, 'f', 'cc', 'ww'}
{'a', 1, 'f', 'cc', 'ww'}

对称差集set1.symmetric_difference(set2)

对称差集(^)  (sysmmetric_difference)
s = set(('a', 'cc', 'f'))
s1 = {'a', 'f', 1, 'ww'}
# 除集合s和集合s1共有的以外的元素
print(s.symmetric_difference(s1))
print(s1^s)
>>>
{1, 'ww', 'cc'}
{1, 'ww', 'cc'}

set.issubset(x) 判断一个集合是否是另一个集合的子集

set.issubset(x)  判断一个集合是否是另一个集合的子集
s = set(('a', 'cc', 'f'))
s1 = {'a', 'f'}
print(s.issubset(s1))
print(s1.issubset(s))
>>>
False
True

set.isuperset(x) 判断一个集合是否是另一个集合的父集

set.isuperset(x)  判断一个集合是否是另一个集合的父集
复制代码
s = set(('a', 'cc', 'f'))
s1 = {'a', 'f'}
print(s.issuperset(s1))
print(s1.issuperset(s))
# s1是s的子集,s是s1的父集
print(s1.issubset(s))
>>>
True
False
True

symmetric_difference_update() 对称更新差集

symmetric_difference_update() 对称更新差集
#将s2更新到s1中的同时去除s2 和s1中相同的元素
s1 = {'ljl','wc','xy','zb','lsy'}
s2 = {'mmf','lsy','syj'}
s1.symmetric_difference_update(s2)
print(s1)
{'syj', 'xy', 'wc', 'ljl', 'zb', 'mmf'}

isdisjoint() 检测2个集合是否不存在交集 存在交集 False

isdisjoint() 检测2个集合是否不存在交集 存在交集 False

s1 = {'ljl','wc','xy','zb','lsy'}
s2 = {'mmf','lsy','syj'}
s3 = {1, 2}
print(s1.isdisjoint(s2))
print(s1.isdisjoint(s3))
>>>
False
True

intersection_update 交集更新操作

intersection_update 交集更新操作
复制代码
s1 = {'ljl','wc','xy','zb','lsy'}
s2 = {'mmf','lsy','syj'}
s1.intersection_update(s2)
s2.intersection_update(s1)
print(s1)
print(s2)
>>>
{'lsy'}
{'lsy'}

参考链接:Python set 方法

Python中字典和集合的区别与联系

字典和集合是进行过性能高度优化的数据结构,特别是对于查找、添加和删除操作。本节将结合实例介绍它们在具体场景下的性能表现,以及与列表等其他数据结构的对比。

例如,有一个存储产品信息(产品 ID、名称和价格)的列表,现在的需求是,借助某件产品的ID找出其价格。则实现代码如下:

def find_product_price(products, product_id):
    for id, price in products:
        if id == product_id:
            return price
    return None
products = [
    (111, 100),
    (222, 30),
    (333, 150)
]
print('The price of product 222 is {}'.format(find_product_price(products, 222)))
>>>

The price of product 222 is 30

在上面程序的基础上,如果列表有 n 个元素,因为查找的过程需要遍历列表,那么最坏情况下的时间复杂度就为 O(n)。即使先对列表进行排序,再使用二分查找算法,也需要 O(logn) 的时间复杂度,更何况列表的排序还需要 O(nlogn) 的时间。

但如果用字典来存储这些数据,那么查找就会非常便捷高效,只需 O(1) 的时间复杂度就可以完成,因为可以直接通过键的哈希值,找到其对应的值,而不需要对字典做遍历操作,实现代码如下:


products = {
  111: 100,
  222: 30,
  333: 150
}
print('The price of product 222 is {}'.format(products[222]))
>>>
The price of product 222 is 30

有些读者可能对时间复杂度并没有直观的认识,没关系,再给大家列举一个实例。下面的代码中,初始化了含有 100,000 个元素的产品,并分别计算出了使用列表和集合来统计产品价格数量的运行时间:

#统计时间需要用到 time 模块中的函数,了解即可
import time
def find_unique_price_using_list(products):
    unique_price_list = []
    for _, price in products: # A
        if price not in unique_price_list: #B
            unique_price_list.append(price)
    return len(unique_price_list)
id = [x for x in range(0, 100000)]
price = [x for x in range(200000, 300000)]
products = list(zip(id, price))
# 计算列表版本的时间
start_using_list = time.perf_counter()
find_unique_price_using_list(products)
end_using_list = time.perf_counter()
print("time elapse using list: {}".format(end_using_list - start_using_list))
#使用集合完成同样的工作
def find_unique_price_using_set(products):
    unique_price_set = set()
    for _, price in products:
        unique_price_set.add(price)
    return len(unique_price_set)
# 计算集合版本的时间
start_using_set = time.perf_counter()
find_unique_price_using_set(products)
end_using_set = time.perf_counter()
print("time elapse using set: {}".format(end_using_set - start_using_set))

运行结果为:

time elapse using list: 68.78650900000001
time elapse using set: 0.010747099999989018

可以看到,仅仅十万的数据量,两者的速度差异就如此之大。而往往企业的后台数据都有上亿乃至十亿数量级,因此如果使用了不合适的数据结构,很容易造成服务器的崩溃,不但影响用户体验,并且会给公司带来巨大的财产损失。

那么,字典和集合为什么能如此高效,特别是查找、插入和删除操作呢?

字典和集合的工作原理

字典和集合能如此高效,和它们内部的数据结构密不可分。不同于其他数据结构,字典和集合的内部结构都是一张哈希表

对于字典而言,这张表存储了哈希值(hash)、键和值这 3 个元素。

对集合来说,哈希表内只存储单一的元素

对于之前版本的 Python 来说,它的哈希表结构如下所示:

| 哈希值 (hash)(key)(value)
. |           ...
0 |    hash0      key0    value0
. |           ...
1 |    hash1      key1    value1
. |           ...
2 |    hash2      key2    value2
. |           ...

这种结构的弊端是,随着哈希表的扩张,它会变得越来越稀疏。比如,有这样一个字典:

{'name': 'mike', 'dob': '1999-01-01', 'gender': 'male'}

那么它会存储为类似下面的形式:

entries = [
['--', '--', '--']
[-230273521, 'dob', '1999-01-01'],
['--', '--', '--'],
['--', '--', '--'],
[1231236123, 'name', 'mike'],
['--', '--', '--'],
[9371539127, 'gender', 'male']
]

显然,这样非常浪费存储空间。为了提高存储空间的利用率,现在的哈希表除了字典本身的结构,会把索引和哈希值、键、值单独分开,也就是采用如下这种结构:

Indices
----------------------------------------------------
None | index | None | None | index | None | index ...
----------------------------------------------------

Entries
--------------------
hash0   key0  value0
---------------------
hash1   key1  value1
---------------------
hash2   key2  value2
---------------------
        ...
---------------------

在此基础上,上面的字典在新哈希表结构下的存储形式为:

indices = [None, 1, None, None, 0, None, 2]
entries = [
[1231236123, 'name', 'mike'],
[-230273521, 'dob', '1999-01-01'],
[9371539127, 'gender', 'male']
]

通过对比可以发现,空间利用率得到很大的提高。

清楚了具体的设计结构,接下来再分析一下如何使用哈希表完成对数据的插入、查找和删除操作。

哈希表插入数据
当向字典中插入数据时,Python 会首先根据键(key)计算出对应的哈希值(通过 hash(key) 函数),而向集合中插入数据时,Python会根据该元素本身计算对应的哈希值(通过 hash(valuse) 函数)。

例如:

dic = {"name":1}
print(hash("name"))
setDemo = {1}
print(hash(1))

运行结果为:

8230115042008314683
1

得到哈希值(例如为 hash)之后,再结合字典或集合要存储数据的个数(例如 n),就可以得到该元素应该插入到哈希表中的位置(比如,可以用 hash%n 的方式)。

如果哈希表中此位置是空的,那么此元素就可以直接插入其中;反之,如果此位置已被其他元素占用,那么 Python 会比较这两个元素的哈希值和键是否相等

  • 如果相等,则表明该元素已经存在,再比较他们的值,不相等就进行更新;

  • 如果不相等,这种情况称为哈希冲突(即两个元素的键不同,但求得的哈希值相同)。这种情况下,Python 会使用开放定址法、再哈希法等继续寻找哈希表中空余的位置,直到找到位置。

具体遇到哈希冲突时,各解决方法的具体含义可阅读《哈希表详解》一节做详细了解。

哈希表查找数据:

在哈希表中查找数据,和插入操作类似,Python 会根据哈希值,找到该元素应该存储到哈希表中的位置,然后和该位置的元素比较其哈希值和键(集合直接比较元素值):

  • 如果相等,则证明找到;

  • 反之,则证明当初存储该元素时,遇到了哈希冲突,需要继续使用当初解决哈希冲突的方法进行查找,直到找到该元素或者找到空位为止。

这里的找到空位,表示哈希表中没有存储目标元素。

哈希表删除元素:
对于删除操作,Python 会暂时对这个位置的元素赋于一个特殊的值,等到重新调整哈希表的大小时,再将其删除。

需要注意的是,哈希冲突的发生往往会降低字典和集合操作的速度。因此,为了保证其高效性,字典和集合内的哈希表,通常会保证其至少留有 1/3 的剩余空间。随着元素的不停插入,当剩余空间小于 1/3 时,Python 会重新获取更大的内存空间,扩充哈希表,与此同时,表内所有的元素位置都会被重新排放。

虽然哈希冲突和哈希表大小的调整,都会导致速度减缓,但是这种情况发生的次数极少。所以,平均情况下,仍能保证插入、查找和删除的时间复杂度O(1)

参考链接:Python字典与集合的异同点

参考链接:Python中字典和集合的区别与联系

解密Python中字典和集合的底层实现,深度分析哈希表

参考链接:《深度剖析CPython解释器》9. 解密Python中字典和集合的底层实现,深度分析哈希表

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yale曼陀罗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值