beck_core/
pmap.rs

1//! A persistent ordered map — the language's `Map[K, V]`.
2//!
3//! # Why this exists
4//!
5//! [`docs/19-phase-1-report.md`](../../../../../docs/19-phase-1-report.md) §19.4 item 3: the fold was
6//! `O(events × rows)` because `map_insert` cloned the whole accumulator. `Arc<BTreeMap>` makes
7//! *cloning the handle* cheap and *updating* expensive, which is exactly backwards for a language
8//! whose central construct is `state = fold(f, init, events)`.
9//!
10//! The Phase 1 report proposed uniqueness analysis — let the fold mutate in place when the previous
11//! state is dead. That is worth having eventually, but it is the wrong *first* answer: it makes an
12//! asymptotic guarantee depend on an optimisation firing. A persistent map gives `O(log n)` updates
13//! unconditionally, with or without the analysis, on every backend. Uniqueness analysis then turns
14//! `O(log n)` into `O(1)` amortised for the common case, which is a real but secondary win.
15//!
16//! # Why it is written here rather than depended on
17//!
18//! `im` and `rpds` are both MPL-2.0, which `deny.toml` does not allow. More to the point, a
19//! persistent map is not a third-party concern for a functional language — it is `Map[K, V]`, a
20//! type in the surface language, and its performance characteristics are part of the semantics.
21//! `docs/01-vision-and-premise.md` §1.5's "we do not write a storage engine" is about substrates,
22//! not about the standard library's own data structures.
23//!
24//! # Why *this* structure
25//!
26//! Three requirements fix the answer, and it is worth writing down which:
27//!
28//! 1. **Persistent.** `state = fold(f, init, events)` keeps old states reachable — snapshots hold
29//!    them, `replay_to` rebuilds them, the differential harness diffs against them. So updating
30//!    cannot mean mutating.
31//! 2. **Ordered by key.** Iteration order reaches the rendered page and the state digest, and
32//!    §4.8's replay harness compares both bit for bit.
33//! 3. **Keys are arbitrary [`Value`](crate::Value)s** — not integers, so no Patricia trie; ordered
34//!    by comparison, so no hash table.
35//!
36//! (2) and (3) together mean a comparison-based ordered dictionary, whose worst case is
37//! `Ω(log n)` per operation on information-theoretic grounds. `O(log n)` is therefore optimal and
38//! the only question left is *which* balanced search tree. Three are plausible:
39//!
40//! | scheme          | height     | `len` | used by                        |
41//! |-----------------|------------|-------|--------------------------------|
42//! | AVL             | ≤1.44 lg n | `O(n)`| OCaml `Map`                    |
43//! | red-black       | ≤2 lg n    | `O(n)`| Scala, Java `TreeMap`          |
44//! | weight-balanced | ≤2.4 lg n  | `O(1)`| Haskell `Data.Map`, SML/NJ     |
45//!
46//! Weight-balanced wins here for a language-specific reason: `map_len` is a *prim*, so a program
47//! may call it inside a view that already runs once per event. Every node carries its subtree size,
48//! so `len` is a field read rather than a traversal. The same sizes give `O(log n)` rank/select if
49//! indexing is ever added, and admit the join-based `union`/`intersection`/`difference` of Blelloch,
50//! Ferizovic and Sun (2016) at the optimal `O(m log(n/m + 1))` if those become prims.
51//!
52//! A HAMT (Bagwell 2001; CHAMP, Steindorfer and Vinju 2015) would be a constant factor faster —
53//! depth ≤7 rather than ~2.4 lg n — but iterates in *hash* order, which violates (2); recovering
54//! key order would cost a sort on every render, and the digest would then depend on a hash function
55//! staying stable across compiler versions. It buys no asymptotic improvement, so it loses.
56//!
57//! # Cost
58//!
59//! | operation                    | time       | *fresh* nodes |
60//! |------------------------------|------------|---------------|
61//! | `get`, `contains_key`        | `O(log n)` | 0             |
62//! | `insert`, `remove`           | `O(log n)` | `O(log n)`    |
63//! | `len`, `is_empty`, `clone`   | `O(1)`     | 0             |
64//! | `iter`, `keys`, `values`     | `O(n)`     | 0             |
65//!
66//! So a fold of `E` events over a map reaching `n` entries costs `O(E log n)` time, against the
67//! `O(E · n)` — quadratic when every event adds a row — that copying cost. Live space is `O(n)`
68//! nodes plus `O(log n)` per retained version: the path a superseded version rebuilt is freed by
69//! its `Arc` the moment the old state is dropped.
70//!
71//! The price of sharing is per-entry overhead: a node is key + value + `usize` + two `Option<Arc>`
72//! plus the `Arc` header, roughly 3–5× a `BTreeMap` entry, which packs ~11 entries to a cache line
73//! group. That is the trade — and it is repaid immediately, because the old code allocated a whole
74//! copy of the map on *every* event.
75//!
76//! The remaining constant-factor win is uniqueness: when the previous state is dead, the path could
77//! be updated in place (`Arc::get_mut` — Clojure's transients) for `O(1)` allocation. That is a
78//! real improvement and it is *not* implemented here, because an asymptotic guarantee should not
79//! depend on an optimisation firing.
80
81use std::cmp::Ordering;
82use std::sync::Arc;
83
84/// The rebalancing constants. `DELTA` bounds how lopsided a node may be; `RATIO` decides single
85/// versus double rotation.
86///
87/// ⟨3, 2⟩ is not a free choice. Adams' "Efficient sets: a balancing act" (1993) published ⟨4, 2⟩,
88/// and Hirai and Yamamoto ("Balancing weight-balanced trees", JFP 21(3), 2011) later proved by
89/// exhaustive machine-checked search that ⟨4, 2⟩ does **not** preserve the invariant under delete —
90/// a real bug that shipped in Haskell's `containers`. ⟨3, 2⟩ is one of the pairs they proved valid
91/// for both insert and delete, which is why it is the pair here.
92const DELTA: usize = 3;
93const RATIO: usize = 2;
94
95#[derive(Debug)]
96struct Node<K, V> {
97    key: K,
98    value: V,
99    size: usize,
100    left: Option<Arc<Node<K, V>>>,
101    right: Option<Arc<Node<K, V>>>,
102}
103
104type Link<K, V> = Option<Arc<Node<K, V>>>;
105
106fn size<K, V>(n: &Link<K, V>) -> usize {
107    n.as_ref().map_or(0, |n| n.size)
108}
109
110/// A persistent ordered map. Cloning is `O(1)` and shares everything.
111#[derive(Debug)]
112pub struct PMap<K, V> {
113    root: Link<K, V>,
114}
115
116impl<K, V> Clone for PMap<K, V> {
117    fn clone(&self) -> Self {
118        PMap {
119            root: self.root.clone(),
120        }
121    }
122}
123
124impl<K, V> Default for PMap<K, V> {
125    fn default() -> Self {
126        PMap { root: None }
127    }
128}
129
130impl<K: Ord + Clone, V: Clone> PMap<K, V> {
131    pub fn new() -> PMap<K, V> {
132        PMap::default()
133    }
134
135    pub fn len(&self) -> usize {
136        size(&self.root)
137    }
138
139    pub fn is_empty(&self) -> bool {
140        self.root.is_none()
141    }
142
143    pub fn get(&self, key: &K) -> Option<&V> {
144        let mut cur = self.root.as_ref();
145        while let Some(n) = cur {
146            cur = match key.cmp(&n.key) {
147                Ordering::Less => n.left.as_ref(),
148                Ordering::Greater => n.right.as_ref(),
149                Ordering::Equal => return Some(&n.value),
150            };
151        }
152        None
153    }
154
155    pub fn contains_key(&self, key: &K) -> bool {
156        self.get(key).is_some()
157    }
158
159    /// Whether two maps are *the same tree*, not merely equal ones.
160    ///
161    /// `O(1)`, and the answer the incremental engine needs when deciding whether an event moved a
162    /// map at all: structural equality would be `O(n)` per event, which is the cost the engine
163    /// exists to avoid. A `false` here means "it may have changed", never "it did".
164    pub fn same_root(&self, other: &PMap<K, V>) -> bool {
165        match (&self.root, &other.root) {
166            (None, None) => true,
167            (Some(a), Some(b)) => Arc::ptr_eq(a, b),
168            _ => false,
169        }
170    }
171
172    /// Insert, returning a new map. Shares every subtree the new key did not pass through.
173    pub fn insert(&self, key: K, value: V) -> PMap<K, V> {
174        PMap {
175            root: insert_node(&self.root, key, value),
176        }
177    }
178
179    /// Remove, returning a new map.
180    pub fn remove(&self, key: &K) -> PMap<K, V> {
181        PMap {
182            root: remove_node(&self.root, key),
183        }
184    }
185
186    /// Entries in key order.
187    pub fn iter(&self) -> Iter<'_, K, V> {
188        let mut it = Iter { stack: Vec::new() };
189        it.push_left(&self.root);
190        it
191    }
192
193    pub fn keys(&self) -> impl Iterator<Item = &K> {
194        self.iter().map(|(k, _)| k)
195    }
196
197    pub fn values(&self) -> impl Iterator<Item = &V> {
198        self.iter().map(|(_, v)| v)
199    }
200}
201
202/// What happened to one key between two versions of a map.
203///
204/// `old` and `new` are both present for an update, one of them for an insert or a remove. Both
205/// absent never occurs.
206#[derive(Clone, Debug, PartialEq, Eq)]
207pub struct Change<K, V> {
208    pub key: K,
209    pub old: Option<V>,
210    pub new: Option<V>,
211}
212
213impl<K: Ord + Clone, V: Clone + PartialEq> PMap<K, V> {
214    /// The entries that differ between two versions, in key order.
215    ///
216    /// # Why this is `O(δ log n)` rather than `O(n)`
217    ///
218    /// This is the operation the whole incremental view engine rests on
219    /// ([`docs/24-incremental-views-report.md`](../../../../../docs/24-incremental-views-report.md)):
220    /// a fold produces a *whole new accumulator* per event, and a dataflow plan consumes *deltas*,
221    /// so something has to turn one into the other. Comparing entry by entry would be `O(n)` per
222    /// event, which is the recount §3.8 exists to abolish — the plan downstream would be
223    /// incremental and the thing feeding it would not.
224    ///
225    /// [`insert`](PMap::insert) rebuilds only the path to the key and shares every subtree that
226    /// path did not pass through, by `Arc`. So two versions of a map that differ by one insert
227    /// share `n - O(log n)` nodes *by pointer*, and a diff that can recognise a shared subtree can
228    /// skip all of its entries at once.
229    ///
230    /// The traversal is an ordered merge of the two trees, with one extra rule: when the heads of
231    /// the two remaining sequences are the same subtree by pointer, both are dropped. That is sound
232    /// for a reason worth stating, because it is the correctness of the engine: pointer-identical
233    /// subtrees hold identical entries, so the two remaining *sorted sequences* share that prefix
234    /// exactly, and a merge over sorted sequences reports nothing for a shared prefix. It holds
235    /// whatever rebalancing did to the position of that subtree in either tree.
236    pub fn diff(&self, next: &PMap<K, V>) -> Vec<Change<K, V>> {
237        let mut out = Vec::new();
238        let mut a = Walk::new(&self.root);
239        let mut b = Walk::new(&next.root);
240        loop {
241            // The pointer rule, applied before either side is expanded into entries.
242            a.skip_shared(&mut b);
243            match (a.peek(), b.peek()) {
244                (None, None) => return out,
245                (Some((k, v)), None) => {
246                    out.push(Change {
247                        key: k.clone(),
248                        old: Some(v.clone()),
249                        new: None,
250                    });
251                    a.bump();
252                }
253                (None, Some((k, v))) => {
254                    out.push(Change {
255                        key: k.clone(),
256                        old: None,
257                        new: Some(v.clone()),
258                    });
259                    b.bump();
260                }
261                (Some((ka, va)), Some((kb, vb))) => match ka.cmp(kb) {
262                    Ordering::Less => {
263                        out.push(Change {
264                            key: ka.clone(),
265                            old: Some(va.clone()),
266                            new: None,
267                        });
268                        a.bump();
269                    }
270                    Ordering::Greater => {
271                        out.push(Change {
272                            key: kb.clone(),
273                            old: None,
274                            new: Some(vb.clone()),
275                        });
276                        b.bump();
277                    }
278                    Ordering::Equal => {
279                        if va != vb {
280                            out.push(Change {
281                                key: ka.clone(),
282                                old: Some(va.clone()),
283                                new: Some(vb.clone()),
284                            });
285                        }
286                        a.bump();
287                        b.bump();
288                    }
289                },
290            }
291        }
292    }
293}
294
295/// An in-order traversal that can be asked whether its next *subtree* is one another traversal is
296/// also about to yield.
297///
298/// The ordinary [`Iter`] pushes the left spine eagerly, which destroys exactly the information the
299/// diff needs: once a subtree has been expanded into a stack of nodes, "these two are the same
300/// subtree" is no longer a question that can be asked. This keeps unexpanded subtrees on the stack
301/// and expands one only when the merge actually needs an entry from it.
302struct Walk<'a, K, V> {
303    stack: Vec<Task<'a, K, V>>,
304}
305
306enum Task<'a, K, V> {
307    Sub(&'a Arc<Node<K, V>>),
308    Ent(&'a K, &'a V),
309}
310
311impl<'a, K, V> Walk<'a, K, V> {
312    fn new(root: &'a Link<K, V>) -> Walk<'a, K, V> {
313        let mut stack = Vec::new();
314        if let Some(n) = root {
315            stack.push(Task::Sub(n));
316        }
317        Walk { stack }
318    }
319
320    /// Drop any subtree both traversals are about to yield.
321    ///
322    /// Repeated, because skipping one shared subtree can expose another underneath it — which is
323    /// what happens on the second and later events, when the two versions share several whole
324    /// branches rather than one.
325    fn skip_shared(&mut self, other: &mut Walk<'a, K, V>) {
326        loop {
327            let (Some(Task::Sub(x)), Some(Task::Sub(y))) = (self.stack.last(), other.stack.last())
328            else {
329                return;
330            };
331            if !Arc::ptr_eq(*x, *y) {
332                return;
333            }
334            self.stack.pop();
335            other.stack.pop();
336        }
337    }
338
339    /// The next entry, expanding subtrees as needed. Leaves it on the stack.
340    fn peek(&mut self) -> Option<(&'a K, &'a V)> {
341        loop {
342            match self.stack.last()? {
343                Task::Ent(k, v) => return Some((*k, *v)),
344                Task::Sub(n) => {
345                    let n = *n;
346                    self.stack.pop();
347                    // In-order: right subtree deepest, then this entry, then the left subtree.
348                    if let Some(r) = &n.right {
349                        self.stack.push(Task::Sub(r));
350                    }
351                    self.stack.push(Task::Ent(&n.key, &n.value));
352                    if let Some(l) = &n.left {
353                        self.stack.push(Task::Sub(l));
354                    }
355                }
356            }
357        }
358    }
359
360    fn bump(&mut self) {
361        self.stack.pop();
362    }
363}
364
365impl<K: Ord + Clone, V: Clone> FromIterator<(K, V)> for PMap<K, V> {
366    fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
367        iter.into_iter()
368            .fold(PMap::new(), |m, (k, v)| m.insert(k, v))
369    }
370}
371
372fn node<K, V>(key: K, value: V, left: Link<K, V>, right: Link<K, V>) -> Arc<Node<K, V>> {
373    let size = 1 + size(&left) + size(&right);
374    Arc::new(Node {
375        key,
376        value,
377        size,
378        left,
379        right,
380    })
381}
382
383/// Rebuild a node, restoring the weight balance invariant.
384fn balance<K: Clone, V: Clone>(
385    key: K,
386    value: V,
387    left: Link<K, V>,
388    right: Link<K, V>,
389) -> Arc<Node<K, V>> {
390    let (ls, rs) = (size(&left), size(&right));
391    if ls + rs <= 1 {
392        return node(key, value, left, right);
393    }
394    if rs > DELTA * ls {
395        let r = right.as_ref().expect("rs > 0");
396        return if size(&r.left) < RATIO * size(&r.right) {
397            // single left rotation
398            node(
399                r.key.clone(),
400                r.value.clone(),
401                Some(node(key, value, left, r.left.clone())),
402                r.right.clone(),
403            )
404        } else {
405            // double left rotation
406            let rl = r.left.as_ref().expect("checked by the ratio test");
407            node(
408                rl.key.clone(),
409                rl.value.clone(),
410                Some(node(key, value, left, rl.left.clone())),
411                Some(node(
412                    r.key.clone(),
413                    r.value.clone(),
414                    rl.right.clone(),
415                    r.right.clone(),
416                )),
417            )
418        };
419    }
420    if ls > DELTA * rs {
421        let l = left.as_ref().expect("ls > 0");
422        return if size(&l.right) < RATIO * size(&l.left) {
423            node(
424                l.key.clone(),
425                l.value.clone(),
426                l.left.clone(),
427                Some(node(key, value, l.right.clone(), right)),
428            )
429        } else {
430            let lr = l.right.as_ref().expect("checked by the ratio test");
431            node(
432                lr.key.clone(),
433                lr.value.clone(),
434                Some(node(
435                    l.key.clone(),
436                    l.value.clone(),
437                    l.left.clone(),
438                    lr.left.clone(),
439                )),
440                Some(node(key, value, lr.right.clone(), right)),
441            )
442        };
443    }
444    node(key, value, left, right)
445}
446
447fn insert_node<K: Ord + Clone, V: Clone>(link: &Link<K, V>, key: K, value: V) -> Link<K, V> {
448    match link {
449        None => Some(node(key, value, None, None)),
450        Some(n) => Some(match key.cmp(&n.key) {
451            // Only the path to the key is rebuilt; `n.right`/`n.left` are shared by pointer.
452            Ordering::Less => balance(
453                n.key.clone(),
454                n.value.clone(),
455                insert_node(&n.left, key, value),
456                n.right.clone(),
457            ),
458            Ordering::Greater => balance(
459                n.key.clone(),
460                n.value.clone(),
461                n.left.clone(),
462                insert_node(&n.right, key, value),
463            ),
464            Ordering::Equal => node(key, value, n.left.clone(), n.right.clone()),
465        }),
466    }
467}
468
469fn remove_node<K: Ord + Clone, V: Clone>(link: &Link<K, V>, key: &K) -> Link<K, V> {
470    let n = link.as_ref()?;
471    Some(match key.cmp(&n.key) {
472        Ordering::Less => balance(
473            n.key.clone(),
474            n.value.clone(),
475            remove_node(&n.left, key),
476            n.right.clone(),
477        ),
478        Ordering::Greater => balance(
479            n.key.clone(),
480            n.value.clone(),
481            n.left.clone(),
482            remove_node(&n.right, key),
483        ),
484        Ordering::Equal => match (&n.left, &n.right) {
485            (None, None) => return None,
486            (None, Some(r)) => r.clone(),
487            (Some(l), None) => l.clone(),
488            (Some(_), Some(r)) => {
489                // Replace with the successor, then remove it from the right subtree.
490                let (sk, sv) = min_entry(r);
491                balance(sk, sv, n.left.clone(), remove_min(&n.right))
492            }
493        },
494    })
495}
496
497fn min_entry<K: Clone, V: Clone>(n: &Arc<Node<K, V>>) -> (K, V) {
498    let mut cur = n;
499    while let Some(l) = &cur.left {
500        cur = l;
501    }
502    (cur.key.clone(), cur.value.clone())
503}
504
505fn remove_min<K: Clone, V: Clone>(link: &Link<K, V>) -> Link<K, V> {
506    let n = link.as_ref()?;
507    match &n.left {
508        None => n.right.clone(),
509        Some(_) => Some(balance(
510            n.key.clone(),
511            n.value.clone(),
512            remove_min(&n.left),
513            n.right.clone(),
514        )),
515    }
516}
517
518pub struct Iter<'a, K, V> {
519    stack: Vec<&'a Arc<Node<K, V>>>,
520}
521
522impl<'a, K, V> Iter<'a, K, V> {
523    fn push_left(&mut self, mut link: &'a Link<K, V>) {
524        while let Some(n) = link {
525            self.stack.push(n);
526            link = &n.left;
527        }
528    }
529}
530
531impl<'a, K, V> Iterator for Iter<'a, K, V> {
532    type Item = (&'a K, &'a V);
533
534    fn next(&mut self) -> Option<Self::Item> {
535        let n = self.stack.pop()?;
536        self.push_left(&n.right);
537        Some((&n.key, &n.value))
538    }
539}
540
541// ---- equality, ordering and hashing are structural, over the sorted entries ----
542
543impl<K: Ord + Clone + PartialEq, V: Clone + PartialEq> PartialEq for PMap<K, V> {
544    fn eq(&self, other: &Self) -> bool {
545        self.len() == other.len() && self.iter().eq(other.iter())
546    }
547}
548
549impl<K: Ord + Clone, V: Clone + Eq> Eq for PMap<K, V> {}
550
551impl<K: Ord + Clone, V: Clone + Ord> PartialOrd for PMap<K, V> {
552    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
553        Some(self.cmp(other))
554    }
555}
556
557impl<K: Ord + Clone, V: Clone + Ord> Ord for PMap<K, V> {
558    fn cmp(&self, other: &Self) -> Ordering {
559        self.iter().cmp(other.iter())
560    }
561}
562
563#[cfg(test)]
564mod tests {
565    use super::*;
566
567    #[test]
568    fn insert_get_remove_and_ordered_iteration() {
569        let mut m = PMap::new();
570        for i in [5, 3, 8, 1, 4, 7, 9, 2, 6] {
571            m = m.insert(i, i * 10);
572        }
573        assert_eq!(m.len(), 9);
574        assert_eq!(m.get(&4), Some(&40));
575        assert_eq!(m.get(&99), None);
576        assert_eq!(
577            m.keys().copied().collect::<Vec<_>>(),
578            (1..=9).collect::<Vec<_>>()
579        );
580
581        let without = m.remove(&5);
582        assert_eq!(without.len(), 8);
583        assert_eq!(without.get(&5), None);
584        // …and the original is untouched, which is the whole point.
585        assert_eq!(m.get(&5), Some(&50));
586        assert_eq!(m.len(), 9);
587    }
588
589    #[test]
590    fn replacing_a_key_does_not_grow_the_map() {
591        let m = PMap::new().insert("a", 1).insert("a", 2);
592        assert_eq!(m.len(), 1);
593        assert_eq!(m.get(&"a"), Some(&2));
594    }
595
596    #[test]
597    fn an_insert_shares_all_but_the_path_it_rebuilt() {
598        // The property the fold's asymptotics rest on: inserting into a map of n entries allocates
599        // O(log n) nodes, not n. Counted by pointer identity against the original.
600        let mut base = PMap::new();
601        for i in 0..1024 {
602            base = base.insert(i, i);
603        }
604        let next = base.insert(9999, 9999);
605
606        fn nodes<K, V>(link: &Link<K, V>, out: &mut Vec<*const Node<K, V>>) {
607            if let Some(n) = link {
608                out.push(Arc::as_ptr(n));
609                nodes(&n.left, out);
610                nodes(&n.right, out);
611            }
612        }
613        let (mut a, mut b) = (Vec::new(), Vec::new());
614        nodes(&base.root, &mut a);
615        nodes(&next.root, &mut b);
616        let shared = b.iter().filter(|p| a.contains(p)).count();
617        let fresh = b.len() - shared;
618
619        assert_eq!(base.len(), 1024);
620        assert_eq!(next.len(), 1025);
621        assert!(
622            fresh <= 40,
623            "an insert into a 1024-entry map rebuilt {fresh} nodes; O(log n) is ~10-40 with \
624             rebalancing, O(n) would be ~1024"
625        );
626        assert!(
627            shared > 900,
628            "only {shared} of {} nodes were shared",
629            b.len()
630        );
631    }
632
633    #[test]
634    fn the_tree_stays_balanced_under_sorted_insertion() {
635        // Ascending keys are the worst case for a naive BST and the common case for the fold,
636        // because ids often arrive in order.
637        let mut m = PMap::new();
638        for i in 0..10_000 {
639            m = m.insert(i, i);
640        }
641        fn depth<K, V>(link: &Link<K, V>) -> usize {
642            match link {
643                None => 0,
644                Some(n) => 1 + depth(&n.left).max(depth(&n.right)),
645            }
646        }
647        let d = depth(&m.root);
648        assert_eq!(m.len(), 10_000);
649        assert!(
650            d < 40,
651            "depth {d} for 10,000 ascending inserts is not balanced"
652        );
653    }
654
655    #[test]
656    fn removal_keeps_the_map_ordered_and_balanced() {
657        let mut m: PMap<i32, i32> = (0..2_000).map(|i| (i, i)).collect();
658        for i in (0..2_000).step_by(2) {
659            m = m.remove(&i);
660        }
661        assert_eq!(m.len(), 1_000);
662        let keys: Vec<i32> = m.keys().copied().collect();
663        assert!(
664            keys.windows(2).all(|w| w[0] < w[1]),
665            "iteration is not ordered"
666        );
667        assert_eq!(keys[0], 1);
668        assert!(m.get(&0).is_none() && m.get(&1).is_some());
669    }
670
671    /// Every node's subtree size is right, the keys are in order, and neither child outweighs the
672    /// other by more than `DELTA` — the invariant `balance` exists to maintain. Returns the size so
673    /// the check is one pass.
674    fn check_invariant<K: Ord, V>(link: &Link<K, V>, lo: Option<&K>, hi: Option<&K>) -> usize {
675        let Some(n) = link else { return 0 };
676        if let Some(lo) = lo {
677            assert!(&n.key > lo, "key order violated");
678        }
679        if let Some(hi) = hi {
680            assert!(&n.key < hi, "key order violated");
681        }
682        let ls = check_invariant(&n.left, lo, Some(&n.key));
683        let rs = check_invariant(&n.right, Some(&n.key), hi);
684        assert_eq!(n.size, 1 + ls + rs, "a cached subtree size is stale");
685        if ls + rs > 1 {
686            assert!(
687                ls <= DELTA * rs && rs <= DELTA * ls,
688                "weight invariant violated: {ls} against {rs}"
689            );
690        }
691        n.size
692    }
693
694    /// One pseudo-random history of inserts and removes, checked against `BTreeMap` as an oracle
695    /// with the structural invariant verified after *every* operation.
696    fn random_history(seed: u64, steps: u32, keyspace: u32) {
697        use std::collections::BTreeMap;
698        let mut s = seed | 1; // xorshift dies at zero
699        let mut rand = move || {
700            s ^= s << 13;
701            s ^= s >> 7;
702            s ^= s << 17;
703            s
704        };
705
706        let mut ours: PMap<u32, u32> = PMap::new();
707        let mut oracle: BTreeMap<u32, u32> = BTreeMap::new();
708        for step in 0..steps {
709            let k = (rand() % keyspace as u64) as u32;
710            // Insert-heavy, then delete-heavy, so the tree both grows and shrinks through every
711            // rebalancing path rather than hovering at one size.
712            let insert_pct = if step < steps / 2 { 70 } else { 30 };
713            if rand() % 100 < insert_pct {
714                ours = ours.insert(k, step);
715                oracle.insert(k, step);
716            } else {
717                ours = ours.remove(&k);
718                oracle.remove(&k);
719            }
720            check_invariant(&ours.root, None, None);
721            assert_eq!(
722                ours.len(),
723                oracle.len(),
724                "size diverged at step {step}, seed {seed}"
725            );
726        }
727        assert!(
728            ours.iter()
729                .map(|(k, v)| (*k, *v))
730                .eq(oracle.iter().map(|(k, v)| (*k, *v))),
731            "entries diverged from the oracle, seed {seed}"
732        );
733    }
734
735    #[test]
736    fn random_histories_of_inserts_and_removes_match_a_btreemap() {
737        // The class of bug this catches is the one `an_insert_shares_all_but_the_path_it_rebuilt`
738        // cannot: a rotation that loses an entry or breaks the balance invariant only under some
739        // particular interleaving of inserts and deletes. Seeds are fixed, so a failure is a
740        // reproducible failure rather than a story about one; several of them, because a single
741        // history explores one path through `balance` and there are six.
742        //
743        // This is the test that decides whether `DELTA`/`RATIO` are right. It is *not* strong
744        // enough to have found the published ⟨4, 2⟩ counterexample on its own — see the note on
745        // those constants: the parameters rest on Hirai and Yamamoto's proof, and this test
746        // confirms the implementation maintains what they proved maintainable.
747        for seed in 0..32u64 {
748            random_history(seed.wrapping_mul(0x9E37_79B9_7F4A_7C15), 3_000, 256);
749        }
750        // One much larger keyspace, so the tree is deep and mostly-distinct keys rather than a
751        // small set churned repeatedly.
752        random_history(0xDEAD_BEEF, 20_000, 8_192);
753    }
754
755    #[test]
756    fn a_diff_reports_exactly_what_changed() {
757        let base: PMap<i32, i32> = (0..100).map(|i| (i, i)).collect();
758        assert!(base.diff(&base).is_empty());
759
760        let inserted = base.insert(1000, 7);
761        assert_eq!(
762            base.diff(&inserted),
763            vec![Change {
764                key: 1000,
765                old: None,
766                new: Some(7)
767            }]
768        );
769        assert_eq!(
770            inserted.diff(&base),
771            vec![Change {
772                key: 1000,
773                old: Some(7),
774                new: None
775            }]
776        );
777
778        let updated = base.insert(50, -1);
779        assert_eq!(
780            base.diff(&updated),
781            vec![Change {
782                key: 50,
783                old: Some(50),
784                new: Some(-1)
785            }]
786        );
787        // Re-inserting the value it already has is not a change: the engine downstream must not be
788        // told to redo work for an event that moved nothing.
789        assert!(base.diff(&base.insert(50, 50)).is_empty());
790
791        let removed = base.remove(&3);
792        assert_eq!(
793            base.diff(&removed),
794            vec![Change {
795                key: 3,
796                old: Some(3),
797                new: None
798            }]
799        );
800    }
801
802    #[test]
803    fn a_diff_against_an_empty_map_is_every_entry() {
804        let m: PMap<i32, i32> = (0..10).map(|i| (i, i * 2)).collect();
805        let empty = PMap::new();
806        let inserts = empty.diff(&m);
807        assert_eq!(inserts.len(), 10);
808        assert!(inserts.iter().all(|c| c.old.is_none()));
809        // In key order, so a downstream operator can build an ordered arrangement from it without
810        // sorting.
811        assert!(inserts.windows(2).all(|w| w[0].key < w[1].key));
812        assert_eq!(m.diff(&empty).len(), 10);
813    }
814
815    /// The property the incremental view engine's asymptotics rest on: diffing two versions that
816    /// differ by one insert visits `O(log n)` nodes, not `n`.
817    ///
818    /// Counted rather than timed, because a wall-clock assertion in CI is a flake. The counter is
819    /// the number of *entries* the merge had to look at, which is what an `O(n)` implementation
820    /// would drive to `n`.
821    #[test]
822    fn diffing_two_versions_that_share_structure_visits_a_handful_of_entries() {
823        let mut base: PMap<u32, u32> = PMap::new();
824        for i in 0..8192 {
825            base = base.insert(i, i);
826        }
827        let next = base.insert(4096, 999);
828
829        // `Walk` yields entries; count how many either side had to expand.
830        let mut visited = 0usize;
831        let mut a = Walk::new(&base.root);
832        let mut b = Walk::new(&next.root);
833        loop {
834            a.skip_shared(&mut b);
835            match (a.peek(), b.peek()) {
836                (None, None) => break,
837                (Some(_), None) => {
838                    visited += 1;
839                    a.bump();
840                }
841                (None, Some(_)) => {
842                    visited += 1;
843                    b.bump();
844                }
845                (Some((ka, _)), Some((kb, _))) => {
846                    visited += 1;
847                    match ka.cmp(kb) {
848                        Ordering::Less => a.bump(),
849                        Ordering::Greater => b.bump(),
850                        Ordering::Equal => {
851                            a.bump();
852                            b.bump();
853                        }
854                    }
855                }
856            }
857        }
858        // Printed so that docs/24 §24.2's number is reproducible rather than remembered.
859        println!("diffing an 8,192-entry map after one insert looked at {visited} entries");
860        assert!(
861            visited < 64,
862            "diffing an 8192-entry map after one insert looked at {visited} entries; \
863             O(log n) is a few dozen, O(n) would be 8192"
864        );
865        assert_eq!(base.diff(&next).len(), 1);
866    }
867
868    #[test]
869    fn a_diff_matches_a_btreemap_oracle_over_random_histories() {
870        use std::collections::BTreeMap;
871        let mut s = 0x5EED_1234u64;
872        let mut rand = move || {
873            s ^= s << 13;
874            s ^= s >> 7;
875            s ^= s << 17;
876            s
877        };
878        for _ in 0..64 {
879            let mut ours: PMap<u32, u32> = PMap::new();
880            let mut oracle: BTreeMap<u32, u32> = BTreeMap::new();
881            for _ in 0..200 {
882                let k = (rand() % 64) as u32;
883                if rand() % 100 < 60 {
884                    ours = ours.insert(k, (rand() % 8) as u32);
885                    oracle.insert(k, *ours.get(&k).unwrap());
886                } else {
887                    ours = ours.remove(&k);
888                    oracle.remove(&k);
889                }
890            }
891            // A second history from the same start, so the two maps differ in many places at once
892            // rather than by a single path.
893            let mut other = ours.clone();
894            let mut other_oracle = oracle.clone();
895            for _ in 0..60 {
896                let k = (rand() % 64) as u32;
897                if rand() % 100 < 50 {
898                    other = other.insert(k, (rand() % 8) as u32);
899                    other_oracle.insert(k, *other.get(&k).unwrap());
900                } else {
901                    other = other.remove(&k);
902                    other_oracle.remove(&k);
903                }
904            }
905
906            let mut expected: Vec<Change<u32, u32>> = Vec::new();
907            for k in oracle
908                .keys()
909                .chain(other_oracle.keys())
910                .copied()
911                .collect::<std::collections::BTreeSet<_>>()
912            {
913                let (old, new) = (oracle.get(&k).copied(), other_oracle.get(&k).copied());
914                if old != new {
915                    expected.push(Change { key: k, old, new });
916                }
917            }
918            assert_eq!(ours.diff(&other), expected);
919        }
920    }
921
922    #[test]
923    fn equality_and_ordering_are_structural() {
924        let a: PMap<i32, i32> = (0..50).map(|i| (i, i)).collect();
925        // Built in a different order: same map.
926        let b: PMap<i32, i32> = (0..50).rev().map(|i| (i, i)).collect();
927        assert_eq!(a, b);
928        assert_eq!(a.cmp(&b), Ordering::Equal);
929        assert!(a < a.insert(50, 50));
930    }
931}