setdefault 是Python中字典的一个方法,用于在字典中查找指定键,如果键存在,则返回对应值(而不是指定的默认值);如果键不存在,则在字典中添加该键,并将其值设置为指定的默认值
dict.setdefault(key, default_value)
其中,key为要查找或添加的键,default_value为键不存在时设置的默认值(若省略,默认None)
d = {}
d.setdefault("list", []).append(1)
d.setdefault("list", []).append(2)
print(d) # 输出:{"list": [1, 2]}
# 为字典d添加一个键'list',并将其值设置为一个空列表
# 使用列表的append方法向该列表中添加元素1和2
底层原理
Python中字典是一种无序的键值对集合,它的底层实现是通过拉链法实现的哈希表
- 什么是哈希表?一种根据关键字直接访问内存位置的数据结构,通过哈希函数将关键字映射到对应的内存位置
- 什么是拉链法(chaining)?每个哈希桶中存储一个链表, 哈希值相同的键值对会被插入到同一个链表中。当需要查找或插入一个键值对时,先计算出该键的哈希值,然后根据哈希值找到对应的哈希桶,再在该哈希桶对应的链表中查找或插入键值对
- 什么是哈希桶(bucket)?是哈希表中存储元素的容器,是一个动态数组,每个元素都是一个链表,链表中的每个节点都是一个键值对
- 哈希桶的大小通常是一个质数,这是为了保证哈希函数的均匀性和哈希表的性能。如果哈希桶比较小,会导致哈希冲突(hash collision)的概率增大,从而降低哈希表的性能;如果哈希桶比较大,会浪费内存空间
- 哈希桶的大小是根据哈希表的负载因子(load factor)和哈希表中元素数量来计算的。同时是动态调整的,当哈希表中元素数量达到一定阈值时,会自动扩容哈希表,增加哈希桶的数量,在保证哈希函数均匀性和哈希表性能的同时,提高哈希表的存储容量
setdefault方法的底层实现步骤:
- 计算要查找或添加的键的哈希值,找到对应的哈希桶
- 遍历哈希桶对应的链表,查找是否有键与要查找或添加的键相同
- 如果找到了相同的键,则返回该键对应的值
- 如果没有找到相同的键,则在链表头部添加一个新的键值对,并将键的值设置为默认值,返回默认值作为该键的值
与 dict.get() 的区别
dict.get时:
- 若get的键存在,则返回对应键的value,若不存在则返回设置的default的值,默认为None
- 字典中的键值不会发生任何变化
dict.setdefault时:
- 若setdefault的键存在,则返回对应键的value,若不存在则返回设置的default的值,默认为None
- 同时,字典中会将该键及设置的默认值添加到字典中
例1:添加一个新键和默认值
d = {"a": 1, "b": 2}
d.get("c", 3)
print(d) # 输出:{'a': 1, 'b': 2}
d = {"a": 1, "b": 2}
d.setdefault("c", 3)
print(d) # 输出:{'a': 1, 'b': 2, 'c': 3}
# 字典d本来并没有键为'c'的元素,使用setdefault方法可以添加一个新的键'c'和默认值3
例2:修改已有键的默认值
d = {"a": 1, "b": 2}
d.get("a", 3)
print(d) # 输出:{"a": 1, "b": 2}
d = {"a": 1, "b": 2}
d.setdefault("a", 3)
print(d) # 输出:{"a": 1, "b": 2}
# 字典d已经有键'a',使用setdefault方法仍然会返回键'a'对应的值1,而不会修改它的默认值
例3:可以省略默认值参数
d = {"a": 1, "b": 2}
d.get("c")
print(d) # 输出:{"a": 1, "b": 2}
d = {"a": 1, "b": 2}
d.setdefault("c")
print(d) # 输出:{"a": 1, "b": 2, "c": None}
# 省略了默认值参数,此时默认值为None
案例
我有一个字典,为用户评分数据,其中大写字母代表用户user,小写字母代表物品item:
user_score_dict = {
'A': {'a': 3.0, 'b':4.0, 'c':0.0, 'd':3.5, 'e':0.0},
'B': {'a': 4.0, 'b':0.0, 'c':4.5, 'd':0.0, 'e':3.5},
'C': {'a': 0.0, 'b':3.5, 'c':0.0, 'd':0.0, 'e':3.0},
'D': {'a': 0.0, 'b':4.0, 'c':0.0, 'd':3.5, 'e':3.0},
}
基于用户评分数据,构建共现矩阵,代码如下:
- 什么是共现矩阵?行和列都代表物品a~e,元素代表同时喜欢两个物品的用户数
- 显然,共现矩阵对角线全为0,且为对称矩阵
item_user_count = dict() # 每个物品有多少用户产生过行为
count = dict() # 共现矩阵
for user, item in user_score_dict.items():
for i in item.keys():
item_user_count.setdefault(i, 0)
if user_score_dict[user][i] > 0.0:
item_user_count[i] += 1
for j in item.keys():
count.setdefault(i, {}).setdefault(j, 0)
if (user_score_dict[user][i] > 0.0
and user_score_dict[user][j] > 0.0
and i != j):
count[i][j] += 1
结果如下:
item_user_count: {'a': 2, 'b': 3, 'c': 1, 'd': 2, 'e': 3}
# a物品被A和B两个用户交互过,所以为2,以此类推
count: {'a': {'a': 0, 'b': 1, 'c': 1, 'd': 1, 'e': 1},
'b': {'a': 1, 'b': 0, 'c': 0, 'd': 2, 'e': 2},
'c': {'a': 1, 'b': 0, 'c': 0, 'd': 0, 'e': 1},
'd': {'a': 1, 'b': 2, 'c': 0, 'd': 0, 'e': 1},
'e': {'a': 1, 'b': 2, 'c': 1, 'd': 1, 'e': 0}}
# 同时喜欢b和d的用户为A和D,有2个,所以为2,且对称,以此类推
在此基础上,得到物品之间的相似度矩阵,代码如下:
itemSim = dict()
for i, related_items in count.items():
itemSim.setdefault(i, {})
for j, cuv in related_items.items():
itemSim[i].setdefault(j, 0)
itemSim[i][j] = cuv / item_user_count[i] # 同时喜欢物品i和j的用户数 / 喜欢物品i的用户数
# 该公式可以理解为:喜欢物品i的用户中,有多少比例的用户也喜欢物品j
此处也可以写为:
for i, related_items in count.items():
for j, cuv in related_items.items():
itemSim.setdefault(i, {}).setdefault(j, 0)
itemSim[i][j] = cuv / item_user_count[i]
结果如下:
itemSim:
{'a': {'a': 0.0, 'b': 0.5, 'c': 0.5, 'd': 0.5, 'e': 0.5},
'b': {'a': 0.3333333333333333, 'b': 0.0, 'c': 0.0, 'd': 0.6666666666666666, 'e': 0.6666666666666666},
'c': {'a': 1.0, 'b': 0.0, 'c': 0.0, 'd': 0.0, 'e': 1.0},
'd': {'a': 0.5, 'b': 1.0, 'c': 0.0, 'd': 0.0, 'e': 0.5},
'e': {'a': 0.3333333333333333, 'b': 0.6666666666666666, 'c': 0.3333333333333333, 'd': 0.3333333333333333, 'e': 0.0}}