字典和集合

相较于列表和元组,字典的性能更加快,特别在于其增加,修改,删除等操作.字典都能快速完成.而集合与字典的区别主要在于,集合没有键和值的配对.是一个无序的.唯一的元素组合.

创建字典

d1 = {"name": "wp", "age": 18}
d2 = dict({'name': "wp", "age": 18})
d3 = dict([("name", "wp"), ("age", 18)])
d4 = dict(name="wp", age=18)
if d1 == d2 == d3 == d4:
    print('ok')
# ok

创建集合

    s1 = {1,2,3}
    s2 = set([1,2,3])
    if s1 == s2:
        print('ook')

        python中字典和集合,无论是健还是值,都可以是混合的类型

s3 = {1, "wp", 18.5}

元素访问

    d1 = {"name": "wp", "age": 18}
    d2 = dict({'name': "wp", "age": 18})
    d3 = dict([("name", "wp"), ("age", 18)])
    d4 = dict(name="wp", age=18)
    print(d1["name"])
    # wp

        如果没有这个键,则会直接报异常,当然也可以使用get(key,default)函数来进行索引,当健不存在时,咱们可以给其定义一个默认值

    print(d1.get("age"))
    # 18
    print(d1.get('wwww',"null"))
    # null

        集合不支持索引操作,因为集合的本质是一个哈希表,和列表并不一样.所以无法使用s[0]去访问元素,如果要判断元素是否在集合内,只能使用value in dict/set 来判断

元素增删改        

    d1 = {"name": "wp", "age": 18}
    d1['gender'] = "male"
    print(d1)
    # {'name': 'wp', 'age': 18, 'gender': 'male'}
    d1.pop("age")
    print(d1)
    # {'name': 'wp', 'gender': 'male'}
    d1['name'] = "wp1"
    print(d1)
    #{'name': 'wp1', 'gender': 'male'}
    s1 = {1, 2, 3}
    s1.add(4)
    print(s1)
    # {1, 2, 3, 4}
    
    s1.remove(4)
    print(s1)
    # {1, 2, 3}

排序        

    d = {'b': 1, "c": 3, "a": 10}
    d_sort_by_Key = sorted(d.items(), key=lambda x: x[0])
    print(d_sort_by_Key)
    # [('a', 10), ('b', 1), ('c', 3)]
    d_sort_by_value = sorted(d.items(),key=lambda x:x[1])
    print(d_sort_by_Key)
    # [('a', 10), ('b', 1), ('c', 3)]

        这里返回的是一个列表,列表中每个元素,是由原来字典中key,value组成的元组

        但是集合的排序就简单的多,只需要使用sorted()函数即可

    s1 = {1,4,2,6}
    sorted(s1)
    print(s1)
    # {1, 2, 4, 6}

字典与集合的性能对比

        字典和集合都是通过性能高度优化过的数据结构,但是还是有必要在看一下,在一定数据量的前提下二者的性能对比

        假设现在又100000个元素商品,并分别计算使用列表和集合来统计产品价格数量的运行时间

# 列表
def find_unique_price_using_list(products):
    unique_price_list = []
    for _, price in products:  # A
        if price not in unique_price_list:
            unique_price_list.append(price)
        return len(unique_price_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)

if __name__ == '__main__':
    id = [x for x in range(0, 100000)]
    price = [x for x in range(200000, 300000)]
    produces = list(zip(id, price))
    start_using_list = time.perf_counter()
    find_unique_price_using_list(produces)
    end_using_list = time.perf_counter()
    print("list time {}".format(end_using_list - start_using_list))
    # list time 4.9999999999980616e-06

    start_using_list1 = time.perf_counter()
    find_unique_price_using_set(produces)
    end_using_list1 = time.perf_counter()
    print("set time {}".format(end_using_list1 - start_using_list1))
    # set time 0.008161099999999998

        可以看到10w的数据量就有了如此大的性能差异

字典与集合的工作原理

        字典与集合内部的数据结构都是一张哈希表

  • 对于字典而言,这张表存储了哈希值(hash),键和值这3个元素\
  • 对于集合而言,区别就在于表中没有键和值的配对,只有单一的元素

老版本的哈希表结构⬇


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

这种表的最大缺点就是,随着哈希表的扩张,他会变得越来越稀疏,如果数据是这样的话

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

那么他的哈希表结构就是这样

entries = [
['--', '--', '--']
[-230273521, 'dob', '1999-01-01'],
['--', '--', '--'],
['--', '--', '--'],
[1231236123, 'name', 'wp'],
['--', '--', '--'],
[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', 'wp'],
[-230273521, 'dob', '1999-01-01'],
[9371539127, 'gender', 'male']
]

插入操作

        每次向字典或集合插入一个元素时,Python 会首先计算键的哈希值(hash(key)),再和 mask = PyDicMinSize - 1 做与操作,计算这个元素应该插入哈希表的位置 index = hash(key) & mask。如果哈希表中此位置是空的,那么这个元素就会被插入其中。而如果此位置已被占用,Python 便会比较两个元素的哈希值和键是否相等。

  • 若两者都相等,则表明这个元素已经存在,如果值不同,则更新值。
  • 若两者中有一个不相等,这种情况我们通常称为哈希冲突(hash collision),意思是两个元素的键不相等,但是哈希值相等。

        这种情况下,Python 便会继续寻找表中空余的位置,直到找到位置为止。值得一提的是,通常来说,遇到这种情况,最简单的方式是线性寻找,即从这个位置开始,挨个往后寻找空位。让这个步骤更加高效。

查找操作

        查找操作和前面的插入操作类似,Python 会根据哈希值,找到其应该处于的位置;然后,比较哈希表这个位置中元素的哈希值和键,与需要查找的元素是否相等。如果相等,则直接返回;如果不等,则继续查找,直到找到空位或者抛出异常为止。

删除操作

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

不难理解,哈希冲突的发生,往往会降低字典和集合操作的速度。因此,为了保证其高效性,字典和集合内的哈希表,通常会保证其至少留有 1/3 的剩余空间。随着元素的不停插入,当剩余空间小于 1/3 时,Python 会重新获取更大的内存空间,扩充哈希表。不过,这种情况下,表内所有的元素位置都会被重新排放。虽然哈希冲突和哈希表大小的调整,都会导致速度减缓,但是这种情况发生的次数极少。

上一篇:jizz基于构建的服务规范的模型包含两个端处理的流程


下一篇:【20210821 corCTF YauzaCTF】Crypto&OSINT方向部分WP