Skip to main content

ftui_widgets/
fenwick.rs

1//! Cache-friendly Fenwick tree (Binary Indexed Tree) for prefix sums.
2//!
3//! Provides O(log n) point update and prefix query over a contiguous `Vec<u32>`,
4//! with a batch update API that amortises multiple deltas into a single pass.
5//!
6//! # Layout
7//!
8//! The tree is stored 1-indexed in a contiguous `Vec<u32>` of length `n + 1`
9//! (index 0 unused). This gives cache-friendly sequential access and avoids
10//! indirection. For a typical 100k-item list with `u32` heights, the tree
11//! occupies ~400 KB — well within L2 cache on modern CPUs.
12//!
13//! # Operations
14//!
15//! | Operation | Time | Allocations |
16//! |-----------|------|-------------|
17//! | `new(n)` | O(n) | 1 Vec |
18//! | `update(i, delta)` | O(log n) | 0 |
19//! | `prefix(i)` | O(log n) | 0 |
20//! | `range(l, r)` | O(log n) | 0 |
21//! | `batch_update(deltas)` | O(m log n) | 0 |
22//! | `rebuild(values)` | O(n) | 0 |
23//! | `find_prefix(target)` | O(log n) | 0 |
24//!
25//! # Invariants
26//!
27//! 1. `tree[i]` stores the sum of elements in a range determined by `lowbit(i)`.
28//! 2. `prefix(n) == sum of all values`.
29//! 3. After `rebuild`, the tree exactly represents the given values.
30//! 4. `batch_update` produces identical results to sequential `update` calls.
31
32/// Fenwick tree (Binary Indexed Tree) for prefix sum queries over `u32` values.
33///
34/// Designed for virtualized list height layout: each entry `i` stores the height
35/// of item `i`, and `prefix(i)` gives the y-offset of item `i+1`.
36#[derive(Debug, Clone)]
37pub struct FenwickTree {
38    /// 1-indexed tree storage. `tree[0]` is unused.
39    tree: Vec<u32>,
40    /// Number of elements (not including index 0).
41    n: usize,
42}
43
44impl FenwickTree {
45    /// Create a Fenwick tree of size `n` initialised to all zeros.
46    pub fn new(n: usize) -> Self {
47        Self {
48            tree: vec![0u32; n + 1],
49            n,
50        }
51    }
52
53    /// Create a Fenwick tree from an initial array of values in O(n).
54    ///
55    /// This is faster than calling `update` n times (which would be O(n log n)).
56    pub fn from_values(values: &[u32]) -> Self {
57        let n = values.len();
58        let mut tree = vec![0u32; n + 1];
59
60        // Copy values into 1-indexed positions.
61        for (i, &v) in values.iter().enumerate() {
62            tree[i + 1] = v;
63        }
64
65        // Build tree in O(n) using the parent-propagation trick.
66        for i in 1..=n {
67            let parent = i + lowbit(i);
68            if parent <= n {
69                tree[parent] = tree[parent].wrapping_add(tree[i]);
70            }
71        }
72
73        Self { tree, n }
74    }
75
76    /// Number of elements.
77    #[inline]
78    pub fn len(&self) -> usize {
79        self.n
80    }
81
82    /// Whether the tree is empty.
83    #[inline]
84    pub fn is_empty(&self) -> bool {
85        self.n == 0
86    }
87
88    /// Add `delta` to element at position `i` (0-indexed). O(log n), zero alloc.
89    ///
90    /// # Panics
91    /// Panics if `i >= n`.
92    pub fn update(&mut self, i: usize, delta: i32) {
93        assert!(i < self.n, "index {i} out of bounds (n={})", self.n);
94        let mut idx = i + 1; // convert to 1-indexed
95        while idx <= self.n {
96            if delta >= 0 {
97                self.tree[idx] = self.tree[idx].wrapping_add(delta as u32);
98            } else {
99                self.tree[idx] = self.tree[idx].wrapping_sub((-delta) as u32);
100            }
101            idx += lowbit(idx);
102        }
103    }
104
105    /// Set element at position `i` to `value` (0-indexed). O(log n).
106    pub fn set(&mut self, i: usize, value: u32) {
107        let current = self.get(i);
108        let delta = value as i64 - current as i64;
109        self.update(i, delta as i32);
110    }
111
112    /// Get the value at position `i` (0-indexed). O(log n).
113    ///
114    /// Computed as `prefix(i) - prefix(i-1)`.
115    pub fn get(&self, i: usize) -> u32 {
116        if i == 0 {
117            self.prefix(0)
118        } else {
119            self.prefix(i).wrapping_sub(self.prefix(i - 1))
120        }
121    }
122
123    /// Prefix sum of elements [0..=i] (0-indexed). O(log n), zero alloc.
124    ///
125    /// # Panics
126    /// Panics if `i >= n`.
127    pub fn prefix(&self, i: usize) -> u32 {
128        assert!(i < self.n, "index {i} out of bounds (n={})", self.n);
129        let mut sum = 0u32;
130        let mut idx = i + 1; // convert to 1-indexed
131        while idx > 0 {
132            sum = sum.wrapping_add(self.tree[idx]);
133            idx -= lowbit(idx);
134        }
135        sum
136    }
137
138    /// Range sum of elements [left..=right] (0-indexed). O(log n).
139    ///
140    /// # Panics
141    /// Panics if `left > right` or `right >= n`.
142    pub fn range(&self, left: usize, right: usize) -> u32 {
143        assert!(left <= right, "left {left} > right {right}");
144        if left == 0 {
145            self.prefix(right)
146        } else {
147            self.prefix(right).wrapping_sub(self.prefix(left - 1))
148        }
149    }
150
151    /// Total sum of all elements. O(log n).
152    pub fn total(&self) -> u32 {
153        if self.n == 0 {
154            0
155        } else {
156            self.prefix(self.n - 1)
157        }
158    }
159
160    /// Apply multiple updates in a single pass. O(m log n), zero alloc.
161    ///
162    /// Each `(index, delta)` pair is applied sequentially. This produces
163    /// identical results to calling `update` for each pair individually.
164    pub fn batch_update(&mut self, deltas: &[(usize, i32)]) {
165        for &(i, delta) in deltas {
166            self.update(i, delta);
167        }
168    }
169
170    /// Rebuild the tree from a fresh array of values in O(n).
171    ///
172    /// Requires `values.len() == self.n`.
173    ///
174    /// # Panics
175    /// Panics if `values.len() != self.n`.
176    pub fn rebuild(&mut self, values: &[u32]) {
177        assert_eq!(values.len(), self.n, "rebuild size mismatch");
178
179        // Zero out.
180        self.tree.fill(0);
181
182        // Copy into 1-indexed positions.
183        for (i, &v) in values.iter().enumerate() {
184            self.tree[i + 1] = v;
185        }
186
187        // Parent propagation in O(n).
188        for i in 1..=self.n {
189            let parent = i + lowbit(i);
190            if parent <= self.n {
191                self.tree[parent] = self.tree[parent].wrapping_add(self.tree[i]);
192            }
193        }
194    }
195
196    /// Find the largest index `i` such that `prefix(i) <= target`.
197    ///
198    /// Returns `None` if all prefix sums exceed `target` (i.e., `values[0] > target`).
199    /// This is useful for binary-search-by-offset in virtualized lists.
200    /// O(log n), zero alloc.
201    pub fn find_prefix(&self, target: u32) -> Option<usize> {
202        if self.n == 0 {
203            return None;
204        }
205
206        let mut pos = 0usize;
207        let mut remaining = target;
208        let mut bit_mask = most_significant_bit(self.n);
209
210        while bit_mask > 0 {
211            let next = pos + bit_mask;
212            if next <= self.n && self.tree[next] <= remaining {
213                remaining -= self.tree[next];
214                pos = next;
215            }
216            bit_mask >>= 1;
217        }
218
219        if pos == 0 && self.tree.get(1).copied().unwrap_or(u32::MAX) > target {
220            None
221        } else {
222            Some(pos.saturating_sub(1)) // convert back to 0-indexed
223        }
224    }
225
226    /// Resize the tree. If growing, new elements are initialised to 0.
227    /// If shrinking, excess elements are dropped. O(new_n).
228    pub fn resize(&mut self, new_n: usize) {
229        if new_n == self.n {
230            return;
231        }
232        // Extract current values, resize, rebuild.
233        let mut values: Vec<u32> = (0..self.n).map(|i| self.get(i)).collect();
234        values.resize(new_n, 0);
235        self.n = new_n;
236        self.tree = vec![0u32; new_n + 1];
237        self.rebuild(&values);
238    }
239}
240
241/// Lowest set bit of `x`. E.g., `lowbit(6) = 2`, `lowbit(4) = 4`.
242#[inline]
243fn lowbit(x: usize) -> usize {
244    x & x.wrapping_neg()
245}
246
247/// Most significant bit that fits within `n`.
248#[inline]
249fn most_significant_bit(n: usize) -> usize {
250    if n == 0 {
251        return 0;
252    }
253    1 << (usize::BITS - 1 - n.leading_zeros())
254}
255
256#[cfg(test)]
257mod tests {
258    use super::*;
259
260    // ─── Basic construction ───────────────────────────────────────
261
262    #[test]
263    fn new_creates_zeroed_tree() {
264        let ft = FenwickTree::new(10);
265        assert_eq!(ft.len(), 10);
266        assert!(!ft.is_empty());
267        assert_eq!(ft.total(), 0);
268    }
269
270    #[test]
271    fn empty_tree() {
272        let ft = FenwickTree::new(0);
273        assert!(ft.is_empty());
274        assert_eq!(ft.total(), 0);
275    }
276
277    #[test]
278    fn from_values_matches_sequential() {
279        let values = vec![3, 1, 4, 1, 5, 9, 2, 6];
280        let ft = FenwickTree::from_values(&values);
281
282        // Check prefix sums.
283        assert_eq!(ft.prefix(0), 3);
284        assert_eq!(ft.prefix(1), 4);
285        assert_eq!(ft.prefix(2), 8);
286        assert_eq!(ft.prefix(7), 31);
287        assert_eq!(ft.total(), 31);
288    }
289
290    // ─── Point operations ─────────────────────────────────────────
291
292    #[test]
293    fn update_and_query() {
294        let mut ft = FenwickTree::new(5);
295        ft.update(0, 10);
296        ft.update(2, 20);
297        ft.update(4, 30);
298
299        assert_eq!(ft.prefix(0), 10);
300        assert_eq!(ft.prefix(2), 30);
301        assert_eq!(ft.prefix(4), 60);
302        assert_eq!(ft.total(), 60);
303    }
304
305    #[test]
306    fn set_overwrites_value() {
307        let mut ft = FenwickTree::from_values(&[5, 10, 15]);
308        ft.set(1, 20);
309        assert_eq!(ft.get(0), 5);
310        assert_eq!(ft.get(1), 20);
311        assert_eq!(ft.get(2), 15);
312        assert_eq!(ft.total(), 40);
313    }
314
315    #[test]
316    fn get_retrieves_individual_values() {
317        let values = vec![7, 3, 8, 2, 6];
318        let ft = FenwickTree::from_values(&values);
319        for (i, &v) in values.iter().enumerate() {
320            assert_eq!(ft.get(i), v, "mismatch at index {i}");
321        }
322    }
323
324    // ─── Range queries ────────────────────────────────────────────
325
326    #[test]
327    fn range_sum() {
328        let ft = FenwickTree::from_values(&[1, 2, 3, 4, 5]);
329        assert_eq!(ft.range(0, 4), 15);
330        assert_eq!(ft.range(1, 3), 9);
331        assert_eq!(ft.range(2, 2), 3);
332        assert_eq!(ft.range(0, 0), 1);
333    }
334
335    // ─── Batch update ─────────────────────────────────────────────
336
337    #[test]
338    fn unit_batch_update_equivalence() {
339        let values = vec![10, 20, 30, 40, 50];
340        let deltas = vec![(0, 5), (2, -3), (4, 10), (1, 7)];
341
342        // Sequential.
343        let mut ft_seq = FenwickTree::from_values(&values);
344        for &(i, d) in &deltas {
345            ft_seq.update(i, d);
346        }
347
348        // Batch.
349        let mut ft_batch = FenwickTree::from_values(&values);
350        ft_batch.batch_update(&deltas);
351
352        // Must match.
353        for i in 0..5 {
354            assert_eq!(ft_seq.get(i), ft_batch.get(i), "mismatch at index {i}");
355        }
356    }
357
358    // ─── Rebuild ──────────────────────────────────────────────────
359
360    #[test]
361    fn rebuild_matches_from_values() {
362        let v1 = vec![1, 2, 3, 4, 5];
363        let v2 = vec![10, 20, 30, 40, 50];
364
365        let ft1 = FenwickTree::from_values(&v2);
366        let mut ft2 = FenwickTree::from_values(&v1);
367        ft2.rebuild(&v2);
368
369        for i in 0..5 {
370            assert_eq!(ft1.get(i), ft2.get(i));
371        }
372    }
373
374    // ─── find_prefix ──────────────────────────────────────────────
375
376    #[test]
377    fn find_prefix_scroll_offset() {
378        // Heights: [20, 30, 10, 40, 25]
379        // Prefix sums: [20, 50, 60, 100, 125]
380        let ft = FenwickTree::from_values(&[20, 30, 10, 40, 25]);
381
382        // Target 0: first item's offset starts at 0, so prefix(0)=20 > 0 → None?
383        // Actually, find_prefix finds largest i where prefix(i) <= target.
384        // prefix(0) = 20 > 0, so no valid i.
385        assert_eq!(ft.find_prefix(0), None);
386
387        // Target 20: prefix(0)=20 ≤ 20. prefix(1)=50 > 20. → i=0.
388        assert_eq!(ft.find_prefix(20), Some(0));
389
390        // Target 50: prefix(1)=50 ≤ 50. prefix(2)=60 > 50. → i=1.
391        assert_eq!(ft.find_prefix(50), Some(1));
392
393        // Target 99: prefix(2)=60 ≤ 99. prefix(3)=100 > 99. → i=2.
394        assert_eq!(ft.find_prefix(99), Some(2));
395
396        // Target 125: prefix(4)=125 ≤ 125 → i=4.
397        assert_eq!(ft.find_prefix(125), Some(4));
398    }
399
400    // ─── Resize ───────────────────────────────────────────────────
401
402    #[test]
403    fn resize_grow_preserves() {
404        let mut ft = FenwickTree::from_values(&[1, 2, 3]);
405        ft.resize(5);
406        assert_eq!(ft.len(), 5);
407        assert_eq!(ft.get(0), 1);
408        assert_eq!(ft.get(1), 2);
409        assert_eq!(ft.get(2), 3);
410        assert_eq!(ft.get(3), 0);
411        assert_eq!(ft.get(4), 0);
412    }
413
414    #[test]
415    fn resize_shrink_drops() {
416        let mut ft = FenwickTree::from_values(&[1, 2, 3, 4, 5]);
417        ft.resize(3);
418        assert_eq!(ft.len(), 3);
419        assert_eq!(ft.total(), 6); // 1+2+3
420    }
421
422    // ─── Property: prefix sum correctness ─────────────────────────
423
424    #[test]
425    fn property_prefix_sum_correct() {
426        // Deterministic PRNG for random updates.
427        let mut seed: u64 = 0xCAFE_BABE_0000_0001;
428        let n = 100;
429        let mut naive = vec![0u32; n];
430        let mut ft = FenwickTree::new(n);
431
432        for _ in 0..500 {
433            seed = seed
434                .wrapping_mul(6364136223846793005)
435                .wrapping_add(1442695040888963407);
436            let idx = (seed >> 33) as usize % n;
437            seed = seed
438                .wrapping_mul(6364136223846793005)
439                .wrapping_add(1442695040888963407);
440            let delta = ((seed >> 33) as i32 % 100).abs();
441
442            naive[idx] = naive[idx].wrapping_add(delta as u32);
443            ft.update(idx, delta);
444        }
445
446        // Verify all prefix sums match naive computation.
447        let mut naive_prefix = 0u32;
448        for (i, value) in naive.iter().enumerate() {
449            naive_prefix = naive_prefix.wrapping_add(*value);
450            assert_eq!(ft.prefix(i), naive_prefix, "prefix mismatch at index {i}");
451        }
452    }
453
454    // ─── Performance test ─────────────────────────────────────────
455
456    #[test]
457    fn perf_fenwick_hotpath() {
458        let n = 100_000;
459        let values: Vec<u32> = (0..n).map(|i| (i % 50 + 1) as u32).collect();
460
461        // Build.
462        let start = std::time::Instant::now();
463        let mut ft = FenwickTree::from_values(&values);
464        let build_time = start.elapsed();
465
466        // 10k point updates.
467        let start = std::time::Instant::now();
468        for i in 0..10_000 {
469            ft.update(i * 10, 5);
470        }
471        let update_time = start.elapsed();
472
473        // 10k prefix queries.
474        let start = std::time::Instant::now();
475        let mut _sink = 0u32;
476        for i in 0..10_000 {
477            _sink = _sink.wrapping_add(ft.prefix(i * 10));
478        }
479        let query_time = start.elapsed();
480
481        // Batch update: 10k deltas.
482        let deltas: Vec<(usize, i32)> = (0..10_000).map(|i| (i * 10, 3)).collect();
483        let start = std::time::Instant::now();
484        ft.batch_update(&deltas);
485        let batch_time = start.elapsed();
486
487        // Log results.
488        eprintln!("=== Fenwick Tree Performance (n={n}) ===");
489        eprintln!("Build (from_values):  {:?}", build_time);
490        eprintln!("10k point updates:    {:?}", update_time);
491        eprintln!("10k prefix queries:   {:?}", query_time);
492        eprintln!("10k batch updates:    {:?}", batch_time);
493        eprintln!("Query p50 (approx):   {:?}", query_time / 10_000);
494
495        // Budget assertions.
496        assert!(
497            query_time < std::time::Duration::from_millis(50),
498            "10k queries too slow: {query_time:?}"
499        );
500        assert!(
501            build_time < std::time::Duration::from_millis(100),
502            "build too slow: {build_time:?}"
503        );
504    }
505
506    // ─── Helpers ──────────────────────────────────────────────────
507
508    #[test]
509    fn lowbit_correctness() {
510        assert_eq!(lowbit(1), 1);
511        assert_eq!(lowbit(2), 2);
512        assert_eq!(lowbit(3), 1);
513        assert_eq!(lowbit(4), 4);
514        assert_eq!(lowbit(6), 2);
515        assert_eq!(lowbit(8), 8);
516        assert_eq!(lowbit(12), 4);
517    }
518
519    #[test]
520    fn msb_correctness() {
521        assert_eq!(most_significant_bit(0), 0);
522        assert_eq!(most_significant_bit(1), 1);
523        assert_eq!(most_significant_bit(5), 4);
524        assert_eq!(most_significant_bit(8), 8);
525        assert_eq!(most_significant_bit(100), 64);
526        assert_eq!(most_significant_bit(1000), 512);
527    }
528}