哈希表-set和哈希表原理

标签(空格分隔): python

目录

  • 哈希表-set和哈希表原理
  • 1,集合set
  • 1.1 初始化
  • 1.2 元素性质
  • 1.3 增加
  • 1.4 删除
  • 1.5 修改
  • 1.6 索引
  • 1.7 成员运算符in
  • 1.7.1 IPython魔术方法
  • 1.7.2 set和线性结构比较
  • 1.8 遍历
  • 1.9 可哈希
  • 1.10 集合概念
  • 1.11 并集
  • 1.12 交集
  • 1.13 差集
  • 1.14 对称差集
  • 1.15 其他集合运算
  • 1.16 集合练习题
  • 2,字典Dict
  • 2.1 舒适化
  • 2.2 元素访问
  • 2.3 新增和修改
  • 2.4 删除
  • 2.5 遍历
  • 2.5.1 遍历key
  • 2.5.2 遍历value
  • 2.5.3 遍历item
  • 2.6 遍历和删除
  • 2.7 key
  • 2.8 有序性

1,集合set

集合,简称集。由任意个元素构成的集体。高级语言都实现了这个非常重要的数据结构类型。python中,它是**可变的、无序的、不可重复的元素的集合。

1.1 初始化

  • set() -> new empty set object
  • set(iterable) -> new set object
s1 = set()
s2 = set(range(5))
s3 = set([1, 2, 3]))
s4 = set('abcd')

s5 = {} # 这个不是集合,而是字典

s6 = {1, 2, 3}
s7 = {1, (1,)}
s8 = {1, (1,), [1]} # 报错,列表不可哈希

1.2 元素性质

  • 去重:在集合中,所有元素必须互不相同
  • 无需: 因为无序,所以不可索引
  • 可哈希: python集合中的元素必须可以hash,即元素都可以使用内建函数hash
  • 目前学过的不可hash的类型有:list、set、bytearray、字典
  • 可迭代:set中虽然元素不一样,但是元素都可以迭代出来

1.3 增加

  • add(elem)
  • 增加一个元素到set中
  • 如果元素存在,什么都不做
  • update(*others)
  • 合并其他元素到set集合中来
  • 参数others必须是可迭代对象
  • 就地修改
s = set()
s.add(1)
s.update((1, 2, 3), [2, 3, 4])

1.4 删除

  • remove(elem)
  • 从set中移除一个元素
  • 元素不存在,抛出异常KeyError异常,为什么是KeyError异常?
  • discard(elem)
  • 从set中移除一个元素
  • 元素不存在,什么都不做(不报异常)
  • pop() -> item
  • 移除并返回任意的元素,为什么是任意的元素
  • 空集返回KeyError异常
  • clear()
  • 移除所有元素
s = set(range(10))
s.remove(0)
# s.remove(11) # KeyError为什么
s.discard(11)
s.pop()
s.clear()

1.5 修改

集合类型没有修改。因为元素唯一。如果元素能够加入到集合中,说明它和别的元素不一样。所谓修改,其实就是把当前元素修改成一个完全不同的元素,就是删除加入新元素。

1.6 索引

非线性结构,不可索引。

1.7 成员运算符in

print(10 in [1, 2, 3])
print(10 in {1, 2, 3})

上面两句代码,分别在列表和集合中搜索元素,如果列表和集合的元素都有100万个,谁的效率高?(集合的效率高)

1.7.1 IPython魔术方法

IPython内置的特殊方法,使用%百分号开头的

  • % 开头是line magic
  • %% 开头是cell magic, notebook的cell
%timeit statement
-n 一个循环loop执行语句多少次
-r 循环执行多少次loop,取最好的结果

%%timeit setup_code
    * code......
    
# 下面写一行,列表每次都要创建,这样写不好
%timeit (-1 in list(range(100)))

# 下面写在一个cell中,写在setup中,列表创建一次
%%timeit l = list(range(1000000))
-1 in l

1.7.2 set和线性结构比较

%%timeit lst1 = list(range(100))
a = -1 in lst1
1.07 µs ± 35.6 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%%timeit lst1 = list(range(1000000))
a = -1 in lst1
10.4 ms ± 45.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)


%%timeit set1 = set(range(100))
a = -1 in set1
30.7 ns ± 2.25 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

%%timeit set1 = set(range(1000000))
a = -1 in set1
32.7 ns ± 2.72 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

从结果来看,集合的性能很好。

  • 线性数据结构,搜索元素的时间复杂度是O(n),即随着数据规模的扩大耗时增大
  • set、dict使用hash表实现,内部使用hash值作为key,时间复杂度是O(1),查询时间和数据规模没有关系,不会随着数据规模增大而搜索性能下降。

1.8 遍历

只要是容器,都可以遍历元素。但是效率都是O(n)

1.9 可哈希

  • 数值型int、float、complex
  • 布尔型True、False
  • 字符串string、bytes
  • tuple
  • None
  • 以上都是不可变类型,称为可哈希类型,hashable
    set元素必须是可hash的。

1.10 集合概念

  • 全集
  • 所有元素的集合。例如实数集,所有实数组成的集合就是全集。
  • 子集suset和超集superset
  • 一个集合A所有元素都在另一个集合B内,A是B的子集,B是A的超集
  • 真子集和真超集
  • A是B的子集,且A不等于B,A就是B的真子集,B是A的真超集
  • 并集
  • 多个集合合并的结果
  • 交集
  • 多个集合的公共部分
  • 差集
  • 集合中除去和其他集合公共部分

集合操作类型

集合操作

操作方法

方法运算符

并集

union(*others)

|

update(*others)

|=

交集

intersection(*others)

&

intersection_update(*others)

&=

差集

difference(*others)

-

difference_update(*others)

-=

对称差集

symmetric_difference(*other)

^

symmetric_difference_update(*other)

^=

1.11 并集

将两个集合A和B的所有的元素合并到一起,组成的集合称作集合A和集合B的并集

  • union(*others) 返回和多个集合合并后的新的集合
  • | 运算符重载,等同于union
  • update(*others) 和多个集合合并,就地修改
  • |= 等同于update

1.12 交集

集合A和集合B,由所有属于A且属于B的元素组成的集合

  • intersection(*others) 返回和多个集合的交集
  • & 等同 intersection
  • intersection_update(*others) 获取和多个集合的交集,并就地修改
  • &= 等同 intersection_update

1.13 差集

集合A和B,由所有属于A且不属于B的元素组成的集合

  • difference(*others) 返回和多个集合的差集
  • - 等同 difference
  • difference_update(*others) 获取和多个集合的差集并就地修改
  • -= 等同 difference_update

1.14 对称差集

集合A和集合B,由所有不属于A和B的交集元素组成的集合,记作 (A - B) U (B - A)

  • symmetric_difference(*other) 返回和另一个集合的对称差集
  • ^ 等同 symmetric_difference
  • symmetric_difference_update(*other) 获取和另一个集合的对称差集并就地修改
  • ^= 等同 symmetric_difference_update

1.15 其他集合运算

  • issubset(other)、<= 判断当前集合是否是另一个集合的子集
  • set1 < set2 判断set1是否是set2的真子集
  • issuperset(other)、>= 判断当前集合是否是other的超集
  • set1 > set2 判断set1是否是set2的真超集
  • isdisjoint(other) 当前集合和另一个集合没有交集,没有交集,返回True

1.16 集合练习题

一个总任务列表,存储所有任务。一个已完成的任务列表。找出未完成的任务

业务中,任务ID一般不可以重复
所有任务ID放到一个set中,假设为all
所有已完成的任务ID放到一个set中,假设为completed,他是all的子集
uncompleted = all - completed

2,字典Dict

dict 即 dictionary,也称为mapping
python中,字典由任意个元素构成的结合,,每一个元素称为Item,也称Entry。这个Item是由(key,value)组成的二元组。
字典是可变的、无序的、key不可重复的key-value pairs键值对集合。

2.1 舒适化

  • dict(**kwargs) 使用name=value对初始化一个字典
  • dict(iterable, **kwarg) 使用可迭代对象和name=value对构造字典,不过可迭代对象的元素必须是一个二元结构
  • dict(mapping, **kwarg) 使用一个字典构造另一个字典

字典的初始化方法都非常常用,都需要会用

d1 = {}
d2 = dict()
d3 = dict(a=100, b=200)
d4 = dict(d3)
d5 = dict(d4, a=300, c=400)
d6 = dict([['a', 100], ['b', 200], ['c', 300]], b='abc', d=111)

# 类方法 dict.fromkeys(iteralbe, value)
d = dict.fromkeys(range(5))
d = dict.fromkeys(range(5), 0)

2.2 元素访问

  • d[key]
  • 返回key对应的值value
  • key不存在抛出KeyError异常
  • get(key[, default])
  • 返回key对应的值value
  • key不存在返回缺省值,如果没有设置缺省值就返回None
  • setdefault(key[, default])
  • 返回key对应的值value
  • key不存在,添加kv对,value设置为default,并返回default,如果default没有设置,缺省值为None

2.3 新增和修改

  • d[key] = value
  • 将key对应的值修改为value
  • key不存在添加新的kv对
  • update([other]) -> None
  • 使用另一个字典的kv对来更新本字典
  • key不存在,就添加
  • key存在,就覆盖已经存在的key对应的值
  • 就地修改
d = {}
d['a'] = 1
d.update(red=1)
d.update([('red',4)])
d.update({'red':3})

2.4 删除

  • pop(key[, default])
  • key存在,移除它,并返回它的value
  • key不存在,返回给定的default
  • default未设置,key不存在则抛出KeyError异常
  • popitem()
  • 移除并返回一个任意的键值对
  • 字典为empty,抛出KeyError异常
  • clear()
  • 清空字典

2.5 遍历

2.5.1 遍历key

for k in d:
    print(k)
    
for k in d.keys():
    print(k)

2.5.2 遍历value

for v in d.values():
    print(v)
    
for k in d.keys():
    print(d[k], d.get(k))

2.5.3 遍历item

for item in d.items():
    print(item)
    print(item[0], item[1])

for k, v in d.items():
    print(k, v)
    
for k, _ in d.items():
    print(k)
    
for _, v in d.items():
    print(v)

查看 数字1 的引用计数(用sys模块)

import sys
sys.getrefcount(1)
d1
{'a': 1, 'b': 2, 'c': 3, 'd': 4}

for k, v in d1.items():
    print(k, d1[k], d1.get(k), d1.setdefault(k))
a 1 1 1
b 2 2 2
c 3 3 3
d 4 4 4

for item in d1.items():
    print(item, type(item))
('a', 1) <class 'tuple'>
('b', 2) <class 'tuple'>
('c', 3) <class 'tuple'>
('d', 4) <class 'tuple'>
d2
{'a': [1, 2], 'b': (3, 4, 5)}
 
for k,(v1, *_, v2) in d2.items(): # 参数解构
    print(k, v1, v2)
a 1 2
b 3 5

在python3中,keys、values、items方法返回一个类似一个生成器的可迭代对象

  • Dictionary view对象,可以使用len()、iter()、in操作
  • 字典的entry的动态视图,字典变化,视图将反映出这些变化
  • keys()返回一个类set对象,也就是可以看做是一个set集合。如果values()都可以hash,那么items()也可以看做是类set对象。

python2中,上面的方法会返回一个心的列表,立即占据新的内存空间,所以python2建议使用iterkeys、itervalues、iteritems版本,返回一个迭代器,而不是返回一个copy

2.6 遍历和删除

# 错误的做法
d = dict(a=1, b=2, c=3)
for k,v in d.items():
    print(d.pop(k))
抛出RuntimeError: dictionary changed size during iteration

在使用keys、values、items方法遍历的时候,不可以改变字典的size

while len(d):
    print(d.popitem())
while d:
    print(d.popitem())

上面的while循环虽然可以移除字典元素,但是很少使用,不如直接clear。

# for 循环正确删除
d = dict(a=1, b=2, c=3)
keys = []
for k,v in d.items():
 keys.append(k)
for k in keys:
    d.pop(k)

集合set在遍历中,也不能改变其长度。

在使用keys、values、items这三个方法迭代时,对字典的长度进行改变,是不可以的

我们可以先用if判断找出我们需要去掉的key,然后将这些key追加到列表中,最后用一个for循环将key去掉

d2
d2
{'a': 1, 'b': 2, 'c': 3}

keys = []
for k, v  in d2.items():
    if v >= 2:
        keys.append(k)
for k in keys:
    d2.pop(k)

d2
{'a': 1}

2.7 key

字典的key和set的元素要求一致

  • set的元素可以就是看做key,set可以看做dict的简化版
  • hashable 可哈希才可以作为key,可以使用hash()测试
  • 使用key访问,就如同列表使用index访问一样,时间复杂度都是O(1),这也是最好的访问元素的方式
d = {
    1 : 0, 
    2.0 : 3, 
    "abc" : None,  
   ('hello', 'world', 'python') : "string", 
    b'abc' : '135'
}

2.8 有序性

字典元素是按照key的hash值无序存储的。
但是,有时候我们却需要一个有序的元素顺序,Python 3.6之前,使用OrderedDict类可以做到,3.6开
始dict自身支持。到底Python对一个无序数据结构记录了什么顺序?

# 3.5如下
C:\Python\Python353>python
Python 3.5.3 (v3.5.3:1880cb95a742, Jan 16 2017, 16:02:32) [MSC v.1900 64 bit 
(AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> d = {'a':300, 'b':200, 'c':100, 'd':50}
>>> d
{'c': 100, 'a': 300, 'b': 200, 'd': 50}
>>> d
{'c': 100, 'a': 300, 'b': 200, 'd': 50}
>>> list(d.keys())
['c', 'a', 'b', 'd']
>>> exit()
C:\Python\Python353>python
Python 3.5.3 (v3.5.3:1880cb95a742, Jan 16 2017, 16:02:32) [MSC v.1900 64 bit 
(AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> d = {'a':300, 'b':200, 'c':100, 'd':50}
>>> d
{'b': 200, 'c': 100, 'd': 50, 'a': 300}

Python 3.6之前,在不同的机器上,甚至同一个程序分别运行2次,都不能确定不同的key的先后顺序

# 3.6+表现如下
C:\Python\python366>python
Python 3.6.6 (v3.6.6:4cf1f54eb7, Jun 27 2018, 03:37:03) [MSC v.1900 64 bit 
(AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> d = {'c': 100, 'a': 300, 'b': 200, 'd': 50}
>>> d
{'c': 100, 'a': 300, 'b': 200, 'd': 50}
>>> exit()

C:\Python\python366>python
Python 3.6.6 (v3.6.6:4cf1f54eb7, Jun 27 2018, 03:37:03) [MSC v.1900 64 bit 
(AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> d = {'c': 100, 'a': 300, 'b': 200, 'd': 50}
>>> d
{'c': 100, 'a': 300, 'b': 200, 'd': 50}
>>> d.keys()
dict_keys(['c', 'a', 'b', 'd'])

Python 3.6+,记录了字典key的录入顺序,遍历的时候,就是按照这个顺序。
如果使用 d = {'a':300, 'b':200, 'c':100, 'd':50} ,就会造成以为字典按照key排序的错觉。
目前,建议不要3.6+提供的这种字典特性,还是认为字典返回的是无序的,可以在Python不同版本中考
虑使用OrderedDict类来保证这种录入序。

from collections import OrderedDict # 从collections这个模块来导入OrderedDict这个类
d3 = OrderedDict()
d3.update(a=1, b=2)
d3
OrderedDict([('a', 1), ('b', 2)])
d3.update({'a':100}, d=4, c=5)
d3
OrderedDict([('a', 100), ('b', 2), ('d', 4), ('c', 5)])