sketch_lossy_counting.nx
buildroot/runtime/sketch_lossy_counting.nx
about
sketch_lossy_counting.nx -- Manku-Motwani 2002 Lossy Counting.
THIRD major frequency-counting algorithm joining CMS / SpaceSaving /
Misra-Gries / CountSketch. Distinct technique: BUCKET-PRUNING.
ALGORITHM:
Stream divided into "buckets" of size w = ceil(1/epsilon).
For each item:
- if tracked: increment frequency
- else: insert with frequency=1, error=current_bucket-1
At each bucket boundary (every w items):
- decrement EVERY tracked item's frequency by 1
- REMOVE items with frequency = 0
GUARANTEE:
stored_freq <= true_freq (underestimate)
stored_freq + error >= true_freq (upper bound)
Items with true_freq > epsilon * N are NEVER missed
No item has stored_freq > true_freq (NEVER overestimates)
COMPARISON to other frequency algorithms:
- CMS: random projection, overestimate-only, O(1) query
- SpaceSaving: k-bounded counters, overestimate-only, top-K native
- Misra-Gries: k-bounded counters, underestimate-only
- CountSketch: median-based, UNBIASED, supports negative
- Lossy Counting (THIS): bucket-pruning, underestimate + explicit
error tracking per entry
MEMORY: O((1/epsilon) log(epsilon * N)) -- bounded but data-dependent.
We use a hash-set with linear probing; cap at NX_LC_MAX_ENTRIES.
dependencies 2 imports · 1 importers
imports: syscalls.nxsketch_types.nx
imported by: sketch_lossy_counting_test.nx
structs
| 39 | struct LcEntry { |
| 45 | struct LossyCounting { |
consts
| 35 | const NX_LC_MIN_EPS_PPM: i64 = 100 // epsilon >= 0.0001 |
| 36 | const NX_LC_MAX_EPS_PPM: i64 = 500000 // epsilon <= 0.5 |
| 37 | const NX_LC_MAX_ENTRIES: i64 = 100000 |
| 56 | const NX_LC_EMPTY_KEY: i64 = 0 // assume real keys != 0 by remapping |
functions
| 60 | func nx_lc_is_pow2(n: i64) -> i64 {
called by 1: nx_lc_alloc |
| 66 | func nx_lc_alloc(capacity: i64, epsilon_ppm: i64) -> *LossyCounting { |
| 95 | func nx_lc_hash(key: i64) -> i64 {
called by 1: nx_lc_probe |
| 100 | func nx_lc_entry_at(lc: *LossyCounting, idx: i64) -> *LcEntry { |
| 107 | func nx_lc_probe(lc: *LossyCounting, key: i64) -> i64 { |
| 138 | func nx_lc_prune(lc: *LossyCounting) -> i64 { |
| 158 | func nx_lc_add(lc: *LossyCounting, key: i64) -> i64 { |
| 189 | func nx_lc_estimate(lc: *LossyCounting, key: i64) -> i64 { |
| 198 | func nx_lc_upper_bound(lc: *LossyCounting, key: i64) -> i64 { |
| 207 | func nx_lc_max_undercount(lc: *LossyCounting) -> i64 {
called by 1: nx_lc_query |
| 211 | func nx_lc_query(lc: *LossyCounting, key: i64) -> *ApproxI64 { |
| 221 | func nx_lc_n_entries(lc: *LossyCounting) -> i64 {
called by 1: main |
| 225 | func nx_lc_total_seen(lc: *LossyCounting) -> i64 { |
| 229 | func nx_lc_memory_bytes(lc: *LossyCounting) -> i64 { |