To track top-k frequent items in a stream with limited memory, Space-Saving maintains at most k counters (item, count, error). For each incoming item x:- If x is tracked, increment its count.- Else if fewer than k counters, insert (x,1,0).- Else replace the counter with the minimum count m: set item=x, count=m+1, error=m. This biases new item's estimate upward by prior minimum; error stores the maximum overestimate possible.Python implementation (compact, production-ready for moderate k):python
import heapq
from collections import defaultdict
class SpaceSaving:
def __init__(self, k):
self.k = k
self.table = {} # item -> (count, error)
self.min_heap = [] # (count, item) min-heap for quick min
def _push_heap(self, item, count):
heapq.heappush(self.min_heap, (count, item))
def _clean_heap(self):
# lazy deletion: pop until heap top matches table count
while self.min_heap:
c, itm = self.min_heap[0]
if itm in self.table and self.table[itm][0] == c:
break
heapq.heappop(self.min_heap)
def update(self, x):
if x in self.table:
c, e = self.table[x]
c += 1
self.table[x] = (c, e)
self._push_heap(x, c)
elif len(self.table) < self.k:
self.table[x] = (1, 0)
self._push_heap(x, 1)
else:
self._clean_heap()
m, victim = heapq.heappop(self.min_heap)
# victim must be valid
if victim in self.table:
_, victim_err = self.table.pop(victim)
# replace with x: count = m+1, error = m
self.table[x] = (m + 1, m)
self._push_heap(x, m + 1)
def estimate(self, x):
return self.table.get(x, (0, 0))[0]
def error(self, x):
return self.table.get(x, (0, 0))[1]
def topk(self):
return sorted([(item, c, e) for item, (c, e) in self.table.items()],
key=lambda t: -t[1])
Key concepts and guarantees:- Each stored count c_x satisfies true_freq(x) >= c_x - error(x) and true_freq(x) <= c_x.- If N is total stream length, Space-Saving guarantees for any reported item c_x >= true_freq(x) and error(x) <= N/(k). More precisely, any reported frequency overestimates true frequency by at most the current minimum count when replaced; worst-case additive error ≤ N/k.- Time per update: O(log k) due to heap; space: O(k).Why effective vs naive:- Naive exact counting needs memory proportional to unique items; infeasible in high-cardinality streams.- Count-Min Sketch uses hashes and gives probabilistic overestimates with tunable error/space and no explicit top-k; extracting top-k from CMS is nontrivial and has larger error for heavy hitters.- Space-Saving directly targets heavy hitters with deterministic bounds, maintains explicit item identities, and yields tight additive error for top items, making it practical for ML feature monitoring, label-frequency tracking, and real-time serving metrics where identifying heavy hitters with limited memory is essential.Edge cases:- Frequent replacements cause higher errors for low-frequency items.- Choose k based on expected heavy-hitter count and acceptable error (e.g., set k = 1/epsilon to bound additive error by epsilon*N).Alternatives:- Frequent algorithm (Misra-Gries) has similar guarantees but slightly different update semantics; CM-Sketch for lower-memory sketches with probabilistic bounds. Space-Saving often gives better practical estimates for top-k identification.