update加索引还是很慢_Counter: += 还是 update?

昨天遇到 collections.Counter 的一个奇怪问题。

问题很简单:我有一堆 Counter,想要把他们相加。下面是我用 IPython 复现的结果:


我一开始直接用 sum 来做,结果效率非常惨。

d0e97a84c70e15f68a4dc854950a4824.png

4000 个长度只有 1 的 Counter,相加竟然用了 2.83 秒!这效率太不可思议了。

我猜测 sum 这么慢因为它可能每次做加法的时候是先求和,再赋值到结果,所以中间创建了许多不必要的临时变量,导致时间复杂度变成 O(n^2)。


所以我改成了手动调用 +=,+= 运算符是原地运算,不存在产生中间变量的消耗,这下应该是 O(n) 了吧。

eb9b9f099e73315157b410dae33cee9c.png

还是很慢!4000 次插入一个元素怎么会用 288 ms?!


那再用 update 函数试一下?与 dict 不同,Counter 的 update 函数是做加法,而不是值替换:

0bb8c4860cd323b5c9e58a8cabceb6a7.png

这下没问题了,只需要 8 ms,update 才是我要的答案。


可是为什么?sum 函数的效率我可以理解,为什么同样是原地操作,+= 比 update 慢了那么多?我去查了 Counter 的文档,然后看到下面这段话:

Several mathematical operations are provided for combining Counterobjects to produce multisets (counters that have counts greater than zero). Addition and subtraction combine counters by adding or subtracting the counts of corresponding elements. Intersection and union return the minimum and maximum of corresponding counts. Each operation can accept inputs with signed counts, but the output will exclude results with counts of zero or less.

大意就是 Counter 可以做加减运算,好像没什么问题。等等?这句话什么意思?

but the output will exclude results with counts of zero or less.

输出的 count 不会有负数?所以说...

让我们来做个实验:

我创建了两个键值互相不重叠的 Counter,然后用 += 把一个加到另一个上面,看看会发生什么结果:

1629b3c232ed9a5afafd4577df4deb40.png

结果不出所料,所有的键值都没有了!也就是说,+= 虽然是原地操作,但是它不光会把右边的 Counter 加到左边的 Counter 上,还会再去检查左边 Counter 的所有键值,一个一个检查是不是小于 0!也就是说,如果右边 Counter 有 1 个键,左边 Counter 有 10000 个键,把它们相加虽然只需要进行一次相加操作,但是加完了 Python 还要做 10000 次操作,试图抹掉原来就小于 0 的值!

这强行提高复杂度的操作...实在是太妙了呀!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值