当字典的键更新时,如何有效地跟踪最大值字典的前k个键?在
我尝试过在每次更新后从字典中创建一个排序列表的天真方法(如Getting key with maximum value in dictionary?中所述),但是这非常昂贵,不能伸缩。在
计算来自无限数据流的词频。在任何给定的时刻,程序都可能被要求报告一个单词是否在当前最常出现的前k个值中。我们如何有效地实现这一目标?在
>>> from itertools import permutations
>>> from collections import Counter
>>> from timeit import timeit
>>> c = Counter()
>>> for x in permutations(xrange(10), 10):
c[x] += 1
>>> timeit('c.most_common(1)', 'from __main__ import c', number=1)
0.7442058258093311
>>> sum(c.values())
3628800
计算这个值需要将近一秒钟!在
我在寻找most_common()
函数的O(1)时间。这应该可以通过另一个只在内部存储当前top-k项并跟踪当前最小值的数据结构来实现。在
我们可以实现一个跟踪top-k值的类,因为我不相信标准库有这个内置的。这将与主dictionary对象(可能是
Counter
)并行地保持最新的。您也可以将其用作主字典对象的子类的属性。在实施
示例用法
^{pr2}$更新值后的访问确实是O(1)
Access是O(1),但是当在一个O(n)操作的set item调用期间最小值发生变化时,其中
n
是顶部值的数目。这仍然比Counter
好,在每次访问过程中,n
是整个词汇表的大小!在collections.Counter.most_common
does a pass over all the values, finding the N-th largest one by putting them in a heap as it goes(我认为,在O(M logn)时间中,M是字典元素的总数)。在正如Wei Yen在评论中所建议的那样,
heapq
可能工作正常:与字典并行,维护N个最大值的heapq
,当您修改dict时,检查该值是否在那里或者现在应该在那里。问题是,正如您所注意到的,接口实际上没有任何方法来修改已经存在的元素的“优先级”(在您的例子中,是[负数,因为这是最小堆数]计数)。在您可以在适当的地方修改相关项,然后运行
heapq.heapify
来恢复heapiness。这需要一个堆大小(N)的线性传递来查找相关的项(除非您要做额外的簿记以将元素与位置关联起来;可能不值得),然后再进行一次线性传递以重新修复。如果某个元素不在列表中,而现在在列表中,则需要通过替换最小的元素(在线性时间内,除非有其他结构),将其添加到堆中。在不过,heapq私有接口包含一个函数^{} ,该函数有以下注释:
听起来不错!调用
heapq._siftdown(heap, 0, pos_of_relevant_idx)
将在logn时间内修复堆。当然,你必须先找到你正在递增的索引的位置,这需要线性时间。您可能需要维护一个元素到索引的字典来避免这种情况(同时保持指向最小元素位置的指针),但是您要么必须复制出_siftdown
的源代码,然后修改它以在字典交换内容时更新字典,或者执行一个线性时间传递来重建字典(但是您只是试图避免线性传递…)。在小心点,这应该算到O(logn)时间了。不过,事实证明,有一种叫做Fibonacci heap的东西确实支持你所需要的所有操作,在(摊余)常量的时间内。不幸的是,这是其中一种情况,big-O不是全部;Fibonacci堆的复杂性意味着在实践中,除了非常大的堆之外,它们实际上并不比二进制堆快。另外,(也许“因此”),在快速Google中没有一个标准的Python实现,虽然Boost C++库确实包含了一个。在
我首先尝试使用
heapq
,对要更改的元素进行线性搜索,然后调用_siftdown
;这是O(N)时间,与Counter
方法的O(mlogn)相比。如果结果太慢,您可以维护额外的索引字典,并创建自己的版本_siftdown
,更新dict,该版本应该结束于O(logn)时间。如果仍然太慢(我对此表示怀疑),您可以寻找一个Python包装器来Boost的Fibonacci堆(或另一个实现),但我真的怀疑这是否值得麻烦。在使用
collections.Counter
它已经在这个真实世界的例子中这样做了。你还有其他的用例吗?在相关问题 更多 >
编程相关推荐