collections.Counter.most_common passes all the values, finding the Nth largest, putting them in the heap when it goes (at, I think, O (M log N) time, where M is the total number of dictionary entries).
heapq , as suggested by Wei Yen in the comments, may work fine: in parallel with the dictionary, support heapq of the N largest values ββand when you change the dict check if the value is there or should be there now. The problem is that, as you already noted, the interface really has no way to change the "priority" (in your case [negative, since it is a mini-heap] number of samples) an existing element.
You can change the corresponding element in place and then run heapq.heapify to restore fragility. It takes a linear passage in heap size (N) to find the corresponding element (unless you are doing an additional account to associate elements with positions, probably not worth it), and another linear passage for re-heapify. In the case when the element was not in the list and now is, you need to add it to the heap, replacing the smallest element (in linear time, with the exception of some additional structure).
The heapq private interface has a _siftdown function that contains this comment:
# 'heap' is a heap at all indices >= startpos, except possibly for pos. pos
That sounds good! A call to heapq._siftdown(heap, 0, pos_of_relevant_idx) will heapq._siftdown(heap, 0, pos_of_relevant_idx) heap in the N time log. Of course, you need to find the position of the index, which you increase in the first place, which takes linear time. You could maintain a dictionary of elements for indexes to avoid this (while also maintaining a pointer to the position of the smallest element), but then you would either have to copy the source _siftdown or change it to update the dictionary when it swaps or performs a linear transition of time to recreate the dictionary afterwards (but you just tried to avoid linear passages ...).
Be careful, this should work before O (log N) time. It turns out, however, that something is called the Fibonacci heap , which supports all the operations you need in a (amortized) constant time. Unfortunately, this is one of those cases where big-O is not the whole story; the complexity of the Fibonacci heap means that in practice, with the possible exception, perhaps, for very large heaps, they are actually no faster than binary heaps. Also (perhaps βthereforeβ), there is no standard Python implementation that I found on the fast search engine, although the Boost C ++ libraries include one.
First I tried to use heapq by doing a linear search for the element you are changing and calling _siftdown ; this time is O (N) compared to O (M log N) for the Counter approach. If this turns out to be too slow, you can save an extra index dictionary and create your own version of _siftdown , which will update the dict, which should end in O (log N). If it's still too slow (which I doubt), you can look for the Python shell for the Boost Fibonacci heap (or another implementation), but I really doubt that it will be worth the hassle.