这里有一个替代解决方案,它既不使用<code>Counter</code>库,也避免了上述嵌套循环。该解决方案通过使用字典作为中间数据结构提高了时间性能,代价是需要额外的内存来存储该字典以换取时间效率的提升(具体取决于您更看重哪一点)。显然,这个方案在代码实现上更为详尽。
def count_nested_values(my_dict: dict, my_list: list):
counts = {}
for list_val in my_list:
if list_val in counts:
counts[list_val] += 1
else:
counts[list_val] = 1
new_dict = {}
for k, dict_val in my_dict.items():
new_dict[k] = 0
# the following loop could be shortened to this line with
# list comprehension, a ternary, and sum()
# new_dict[k] = sum(counts[v] if v in counts else 0 for v in dict_val)
for list_val in dict_val:
if list_val in counts:
new_dict[k] += counts[list_val]
return new_dict
我想强调的是,这个方法其实与wjandrea在上面提供的解决方案本质上相同,只是将Counter
库以及字典推导式替换为了中间字典和循环。这样做有什么好处呢?
在你的解决方案中,你对my_dict
中的每个键执行了循环for element in my_list
,这意味着你在多次不必要的重复处理这些值。另外,在Python中,对于列表来说,使用in
运算符的时间复杂度为O(n),而对于字典和集合则是O(1)。因此,当你执行element in values
时,你会针对my_list
中的每一个元素重新遍历每一个values
列表。
总的来说,这会导致O(n3)的性能表现,通常这是不可取的。其时间复杂度的计算因素如下:
- 外层循环:
my_dict
中的键的数量
- 内层循环:
my_list
中的值的数量
if
块中的in
表达式:嵌套列表中值的数量(对于my_dict
中的每一对键值,嵌套列表大小可能会变化,所以它会随着最大的值列表规模而扩展)
因此,总的迭代次数将是(my_dict中的键数) * (my_list中的值数) * (嵌套列表中的值数)
。在给定的输入示例中,它是3 * 6 * 2 = 36次迭代。
Counter
方案以及我上面提到的解决方案通过以下算法改进了性能:
- 预处理
my_list
以统计my_list
中每个值出现的次数——生成Counter
对象或构建普通的counts
字典的第一层循环。由于字典读写操作是常数时间O(1),预处理的成本是O(n),其中n是my_list
的大小。
- 遍历
my_dict
中的键值对,通过my_dict.items()
实现——在Counter
解决方案中是字典推导式的可迭代部分,在我的方案中是第二个循环。这一遍历的成本是O(n),其中n是my_dict
中键的数量,因此到目前为止,我们的解决方案总体上是O(n),其中n是my_list
的大小或my_dict
中键的数量,取两者较大者。
- 对于
my_dict
中的每一组键值对,遍历嵌套的列表值——在Counter
解决方案中作为sum()
函数的输入,在我的方案中是第二层循环内的嵌套循环。这意味着,包括嵌套循环在内的第二层循环的整体迭代次数现在是my_dict
中的嵌套值总数。因此,现在我们算法的总体性能是O(n),其中n是my_list
的大小或my_dict
中的嵌套值数量,取两者较大者。
- 必须在
counts
字典中查找嵌套值以避免KeyError
。由于counts
是一个字典,查找示例的时间复杂度为O(1),所以不会影响整体效率。
综上所述,最终的性能优化为O(n),而不是O(n3)。
总之,如果你不在乎解决方案的时间效率——比如你知道输入永远不会很大——那么你的解决方案是正确的,并且能够完成任务。但如果你关心解决方案的性能或者出于教育目的,这种实现方式会更加高效一些,同时也大致展示了Counter
解决方案背后的原理(尽管可能在那里还有进一步的优化)。