diff --git a/benchmarks/btreemap/src/main.rs b/benchmarks/btreemap/src/main.rs index 167b973c..efee0740 100644 --- a/benchmarks/btreemap/src/main.rs +++ b/benchmarks/btreemap/src/main.rs @@ -1,6 +1,7 @@ use benchmarks::{random::Random, vec::UnboundedVecN}; use canbench_rs::{bench, bench_fn, BenchResult}; use candid::Principal; +use ic_stable_structures::btreemap::entry::Entry::Occupied; use ic_stable_structures::memory_manager::{MemoryId, MemoryManager}; use ic_stable_structures::{storable::Blob, BTreeMap, DefaultMemoryImpl, Memory, Storable}; use std::ops::Bound; @@ -396,6 +397,45 @@ pub fn btreemap_v2_get_10mib_values() -> BenchResult { }) } +#[bench(raw)] +pub fn btreemap_get_and_incr() -> BenchResult { + let count = 10000; + let mut btree = BTreeMap::new(DefaultMemoryImpl::default()); + let mut rng = Rng::from_seed(0); + let values = generate_random_kv::(count, &mut rng); + for (key, value) in values.iter().copied() { + btree.insert(key, value); + } + + bench_fn(|| { + for (key, _) in values { + let existing = btree.get(&key).unwrap(); + btree.insert(key, existing + 1); + } + }) +} + +#[bench(raw)] +pub fn btreemap_get_and_incr_via_entry() -> BenchResult { + let count = 10000; + let mut btree = BTreeMap::new(DefaultMemoryImpl::default()); + let mut rng = Rng::from_seed(0); + let values = generate_random_kv::(count, &mut rng); + for (key, value) in values.iter().copied() { + btree.insert(key, value); + } + + bench_fn(|| { + for (key, _) in values { + let Occupied(e) = btree.entry(key) else { + panic!() + }; + let existing = e.get(); + e.insert(existing + 1); + } + }) +} + // Benchmarks for `BTreeMap::contains_key`. bench_tests! { // blob K x 128 diff --git a/benchmarks/nns/src/nns_vote_cascading/tests.rs b/benchmarks/nns/src/nns_vote_cascading/tests.rs index a6b7fd2f..2c26b725 100644 --- a/benchmarks/nns/src/nns_vote_cascading/tests.rs +++ b/benchmarks/nns/src/nns_vote_cascading/tests.rs @@ -168,7 +168,7 @@ fn set_up_worst_case( for neuron_id in num_followees..num_neurons { let previous_neuron_ids = (neuron_id - num_half_followees - 1)..neuron_id; let followee_neuron_ids = previous_neuron_ids - .map(|id| NeuronId::from(id)) + .map(NeuronId::from) .chain(not_voting_neuron_ids.clone().into_iter()) .collect::>(); neuron_store.set_followees( diff --git a/src/btreemap.rs b/src/btreemap.rs index c6f4ae92..7e842204 100644 --- a/src/btreemap.rs +++ b/src/btreemap.rs @@ -49,6 +49,7 @@ //! ---------------------------------------- //! ``` mod allocator; +pub mod entry; mod iter; mod node; use crate::btreemap::iter::{IterInternal, KeysIter, ValuesIter}; @@ -59,7 +60,7 @@ use crate::{ }; use allocator::Allocator; pub use iter::Iter; -use node::{DerivedPageSize, Entry, Node, NodeType, PageSize, Version}; +use node::{DerivedPageSize, Node, NodeType, PageSize, Version}; use std::borrow::Cow; use std::marker::PhantomData; use std::ops::{Bound, RangeBounds}; @@ -500,64 +501,112 @@ where pub fn insert(&mut self, key: K, value: V) -> Option { let value = value.into_bytes_checked(); - let root = if self.root_addr == NULL { + let (mut node, search_result) = self.find_node_for_insert(&key); + + match search_result { + Ok(idx) => Some(V::from_bytes(Cow::Owned( + self.update_value(&mut node, idx, value), + ))), + Err(idx) => { + node.insert_entry(idx, (key, value)); + self.save_node(&mut node); + + // Update the length. + self.length += 1; + self.save_header(); + None + } + } + } + + /// Searches for the entry within the map where the key belongs. + /// The value returned is either a `VacantEntry` (if the key doesn't already exist), or an + /// `OccupiedEntry` (if the key does exist). + /// Once you have the entry you can `get` the value, `insert` a new value, or `remove` + /// the value. + pub fn entry(&mut self, key: K) -> entry::Entry { + let (node, search_result) = self.find_node_for_insert(&key); + + match search_result { + Ok(idx) => entry::Entry::Occupied(entry::OccupiedEntry { + map: self, + key, + node, + idx, + }), + Err(idx) => entry::Entry::Vacant(entry::VacantEntry { + map: self, + key, + node, + idx, + }), + } + } + + /// Finds the node the key (and its corresponding value) should be inserted into + /// Return value is a tuple, the first value is the node the key should be inserted into, the + /// seconds value is a `Result` which is `Ok` if the key exists in the node, or `Err` if not. + /// The value in either case is the index at which the key should be inserted. + fn find_node_for_insert(&mut self, key: &K) -> (Node, Result) { + if self.root_addr == NULL { // No root present. Allocate one. let node = self.allocate_node(NodeType::Leaf); self.root_addr = node.address(); self.save_header(); - node - } else { - // Load the root from memory. - let mut root = self.load_node(self.root_addr); + return (node, Err(0)); + } - // Check if the key already exists in the root. - if let Ok(idx) = root.search(&key, self.memory()) { - // Key found, replace its value and return the old one. - return Some(V::from_bytes(Cow::Owned( - self.update_value(&mut root, idx, value), - ))); - } + // Load the root from memory. + let mut root = self.load_node(self.root_addr); - // If the root is full, we need to introduce a new node as the root. - // - // NOTE: In the case where we are overwriting an existing key, then introducing - // a new root node isn't strictly necessary. However, that's a micro-optimization - // that adds more complexity than it's worth. - if root.is_full() { - // The root is full. Allocate a new node that will be used as the new root. - let mut new_root = self.allocate_node(NodeType::Internal); - - // The new root has the old root as its only child. - new_root.push_child(self.root_addr); - - // Update the root address. - self.root_addr = new_root.address(); - self.save_header(); + // Check if the key already exists in the root. + if let Ok(idx) = root.search(key, self.memory()) { + // Key found + return (root, Ok(idx)); + } - // Split the old (full) root. - self.split_child(&mut new_root, 0); + // If the root is full, we need to introduce a new node as the root. + // + // NOTE: In the case where we are overwriting an existing key, then introducing + // a new root node isn't strictly necessary. However, that's a micro-optimization + // that adds more complexity than it's worth. + if root.is_full() { + // The root is full. Allocate a new node that will be used as the new root. + let mut new_root = self.allocate_node(NodeType::Internal); - new_root - } else { - root - } - }; + // The new root has the old root as its only child. + new_root.push_child(self.root_addr); - self.insert_nonfull(root, key, value) - .map(Cow::Owned) - .map(V::from_bytes) + // Update the root address. + self.root_addr = new_root.address(); + self.save_header(); + + // Split the old (full) root. + self.split_child(&mut new_root, 0); + + root = new_root; + } + + self.find_node_for_insert_nonfull(root, key) } - /// Inserts an entry into a node that is *not full*. - fn insert_nonfull(&mut self, mut node: Node, key: K, value: Vec) -> Option> { + /// Finds the node the key (and its corresponding value) should be inserted into + /// + /// PRECONDITION: + /// - `node` is not full. + fn find_node_for_insert_nonfull( + &mut self, + mut node: Node, + key: &K, + ) -> (Node, Result) { // We're guaranteed by the caller that the provided node is not full. assert!(!node.is_full()); // Look for the key in the node. - match node.search(&key, self.memory()) { + match node.search(key, self.memory()) { Ok(idx) => { - // Key found, replace its value and return the old one. - Some(self.update_value(&mut node, idx, value)) + // Key found + (node, Ok(idx)) } Err(idx) => { // The key isn't in the node. `idx` is where that key should be inserted. @@ -565,16 +614,7 @@ where match node.node_type() { NodeType::Leaf => { // The node is a non-full leaf. - // Insert the entry at the proper location. - node.insert_entry(idx, (key, value)); - self.save_node(&mut node); - - // Update the length. - self.length += 1; - self.save_header(); - - // No previous value to return. - None + (node, Err(idx)) } NodeType::Internal => { // The node is an internal node. @@ -583,9 +623,9 @@ where if child.is_full() { // Check if the key already exists in the child. - if let Ok(idx) = child.search(&key, self.memory()) { - // Key found, replace its value and return the old one. - return Some(self.update_value(&mut child, idx, value)); + if let Ok(idx) = child.search(key, self.memory()) { + // Key found + return (child, Ok(idx)); } // The child is full. Split the child. @@ -593,14 +633,14 @@ where // The children have now changed. Search again for // the child where we need to store the entry in. - let idx = node.search(&key, self.memory()).unwrap_or_else(|idx| idx); + let idx = node.search(key, self.memory()).unwrap_or_else(|idx| idx); child = self.load_node(node.child(idx)); } // The child should now be not full. assert!(!child.is_full()); - self.insert_nonfull(child, key, value) + self.find_node_for_insert_nonfull(child, key) } } } @@ -796,25 +836,7 @@ where NodeType::Leaf => { match node.search(key, self.memory()) { Ok(idx) => { - // Case 1: The node is a leaf node and the key exists in it. - // This is the simplest case. The key is removed from the leaf. - let value = node.remove_entry(idx, self.memory()).1; - self.length -= 1; - - if node.entries_len() == 0 { - assert_eq!( - node.address(), self.root_addr, - "Removal can only result in an empty leaf node if that node is the root" - ); - - // Deallocate the empty node. - self.deallocate_node(node); - self.root_addr = NULL; - } else { - self.save_node(&mut node); - } - - self.save_header(); + let value = self.remove_from_leaf_node(node, idx); Some(value) } _ => None, // Key not found. @@ -823,129 +845,8 @@ where NodeType::Internal => { match node.search(key, self.memory()) { Ok(idx) => { - // Case 2: The node is an internal node and the key exists in it. - - let left_child = self.load_node(node.child(idx)); - if left_child.can_remove_entry_without_merging() { - // Case 2.a: A key can be removed from the left child without merging. - // - // parent - // [..., key, ...] - // / \ - // [left child] [...] - // / \ - // [...] [..., key predecessor] - // - // In this case, we replace `key` with the key's predecessor from the - // left child's subtree, then we recursively delete the key's - // predecessor for the following end result: - // - // parent - // [..., key predecessor, ...] - // / \ - // [left child] [...] - // / \ - // [...] [...] - - // Recursively delete the predecessor. - // TODO(EXC-1034): Do this in a single pass. - let predecessor = left_child.get_max(self.memory()); - self.remove_helper(left_child, &predecessor.0)?; - - // Replace the `key` with its predecessor. - let (_, old_value) = node.swap_entry(idx, predecessor, self.memory()); - - // Save the parent node. - self.save_node(&mut node); - return Some(old_value); - } - - let right_child = self.load_node(node.child(idx + 1)); - if right_child.can_remove_entry_without_merging() { - // Case 2.b: A key can be removed from the right child without merging. - // - // parent - // [..., key, ...] - // / \ - // [...] [right child] - // / \ - // [key successor, ...] [...] - // - // In this case, we replace `key` with the key's successor from the - // right child's subtree, then we recursively delete the key's - // successor for the following end result: - // - // parent - // [..., key successor, ...] - // / \ - // [...] [right child] - // / \ - // [...] [...] - - // Recursively delete the successor. - // TODO(EXC-1034): Do this in a single pass. - let successor = right_child.get_min(self.memory()); - self.remove_helper(right_child, &successor.0)?; - - // Replace the `key` with its successor. - let (_, old_value) = node.swap_entry(idx, successor, self.memory()); - - // Save the parent node. - self.save_node(&mut node); - return Some(old_value); - } - - // Case 2.c: Both the left and right child are at their minimum sizes. - // - // parent - // [..., key, ...] - // / \ - // [left child] [right child] - // - // In this case, we merge (left child, key, right child) into a single - // node. The result will look like this: - // - // parent - // [... ...] - // | - // [left child, `key`, right child] <= new child - // - // We then recurse on this new child to delete `key`. - // - // If `parent` becomes empty (which can only happen if it's the root), - // then `parent` is deleted and `new_child` becomes the new root. - assert!(left_child.at_minimum()); - assert!(right_child.at_minimum()); - - // Merge the right child into the left child. - let mut new_child = self.merge( - right_child, - left_child, - node.remove_entry(idx, self.memory()), - ); - - // Remove the right child from the parent node. - node.remove_child(idx + 1); - - if node.entries_len() == 0 { - // Can only happen if this node is root. - assert_eq!(node.address(), self.root_addr); - assert_eq!(node.child(0), new_child.address()); - assert_eq!(node.children_len(), 1); - - self.root_addr = new_child.address(); - - // Deallocate the root node. - self.deallocate_node(node); - self.save_header(); - } else { - self.save_node(&mut node); - } - - self.save_node(&mut new_child); - - // Recursively delete the key. - self.remove_helper(new_child, key) + let value = self.remove_from_internal_node(node, idx, key); + Some(value) } Err(idx) => { // Case 3: The node is an internal node and the key does NOT exist in it. @@ -1150,6 +1051,167 @@ where } } + /// PRECONDITION + /// - `node` is a leaf node + /// - `node.entries_len() > 1` or node is the root node + fn remove_from_leaf_node(&mut self, mut node: Node, idx: usize) -> Vec { + debug_assert_eq!(node.node_type(), NodeType::Leaf); + + // Case 1: The node is a leaf node and the key exists in it. + // This is the simplest case. The key is removed from the leaf. + let value = node.remove_entry(idx, self.memory()).1; + self.length -= 1; + + if node.entries_len() == 0 { + assert_eq!( + node.address(), + self.root_addr, + "Removal can only result in an empty leaf node if that node is the root" + ); + + // Deallocate the empty node. + self.deallocate_node(node); + self.root_addr = NULL; + } else { + self.save_node(&mut node); + } + + self.save_header(); + value + } + + /// PRECONDITION + /// - `node` is an internal node + /// - `node` contains `key` at index `idx` + fn remove_from_internal_node(&mut self, mut node: Node, idx: usize, key: &K) -> Vec { + debug_assert_eq!(node.node_type(), NodeType::Internal); + debug_assert_eq!(node.search(key, self.memory()), Ok(idx)); + + // Case 2: The node is an internal node and the key exists in it. + + let left_child = self.load_node(node.child(idx)); + if left_child.can_remove_entry_without_merging() { + // Case 2.a: A key can be removed from the left child without merging. + // + // parent + // [..., key, ...] + // / \ + // [left child] [...] + // / \ + // [...] [..., key predecessor] + // + // In this case, we replace `key` with the key's predecessor from the + // left child's subtree, then we recursively delete the key's + // predecessor for the following end result: + // + // parent + // [..., key predecessor, ...] + // / \ + // [left child] [...] + // / \ + // [...] [...] + + // Recursively delete the predecessor. + // TODO(EXC-1034): Do this in a single pass. + let predecessor = left_child.get_max(self.memory()); + self.remove_helper(left_child, &predecessor.0).unwrap(); + + // Replace the `key` with its predecessor. + let (_, old_value) = node.swap_entry(idx, predecessor, self.memory()); + + // Save the parent node. + self.save_node(&mut node); + return old_value; + } + + let right_child = self.load_node(node.child(idx + 1)); + if right_child.can_remove_entry_without_merging() { + // Case 2.b: A key can be removed from the right child without merging. + // + // parent + // [..., key, ...] + // / \ + // [...] [right child] + // / \ + // [key successor, ...] [...] + // + // In this case, we replace `key` with the key's successor from the + // right child's subtree, then we recursively delete the key's + // successor for the following end result: + // + // parent + // [..., key successor, ...] + // / \ + // [...] [right child] + // / \ + // [...] [...] + + // Recursively delete the successor. + // TODO(EXC-1034): Do this in a single pass. + let successor = right_child.get_min(self.memory()); + self.remove_helper(right_child, &successor.0).unwrap(); + + // Replace the `key` with its successor. + let (_, old_value) = node.swap_entry(idx, successor, self.memory()); + + // Save the parent node. + self.save_node(&mut node); + return old_value; + } + + // Case 2.c: Both the left and right child are at their minimum sizes. + // + // parent + // [..., key, ...] + // / \ + // [left child] [right child] + // + // In this case, we merge (left child, key, right child) into a single + // node. The result will look like this: + // + // parent + // [... ...] + // | + // [left child, `key`, right child] <= new child + // + // We then recurse on this new child to delete `key`. + // + // If `parent` becomes empty (which can only happen if it's the root), + // then `parent` is deleted and `new_child` becomes the new root. + assert!(left_child.at_minimum()); + assert!(right_child.at_minimum()); + + // Merge the right child into the left child. + let mut new_child = self.merge( + right_child, + left_child, + node.remove_entry(idx, self.memory()), + ); + + // Remove the right child from the parent node. + node.remove_child(idx + 1); + + if node.entries_len() == 0 { + // Can only happen if this node is root. + assert_eq!(node.address(), self.root_addr); + assert_eq!(node.child(0), new_child.address()); + assert_eq!(node.children_len(), 1); + + self.root_addr = new_child.address(); + + // Deallocate the root node. + self.deallocate_node(node); + self.save_header(); + } else { + self.save_node(&mut node); + } + + self.save_node(&mut new_child); + + // Recursively delete the key. + self.remove_helper(new_child, key).unwrap() + } + /// Returns an iterator over the entries of the map, sorted by key. /// /// # Example @@ -1272,7 +1334,7 @@ where /// Output: /// [1, 2, 3, 4, 5, 6, 7] (stored in the `into` node) /// `source` is deallocated. - fn merge(&mut self, source: Node, mut into: Node, median: Entry) -> Node { + fn merge(&mut self, source: Node, mut into: Node, median: node::Entry) -> Node { into.merge(source, median, &mut self.allocator); into } diff --git a/src/btreemap/entry.rs b/src/btreemap/entry.rs new file mode 100644 index 00000000..eb6b103b --- /dev/null +++ b/src/btreemap/entry.rs @@ -0,0 +1,131 @@ +use crate::btreemap::node::{Node, NodeType}; +use crate::{BTreeMap, Memory, Storable}; +use std::borrow::Cow; + +pub enum Entry<'a, K: 'a + Storable + Ord + Clone, V: 'a + Storable, M: Memory> { + Vacant(VacantEntry<'a, K, V, M>), + Occupied(OccupiedEntry<'a, K, V, M>), +} + +pub struct VacantEntry<'a, K: 'a + Storable + Ord + Clone, V: 'a + Storable, M: Memory> { + pub(crate) map: &'a mut BTreeMap, + pub(crate) key: K, + pub(crate) node: Node, + pub(crate) idx: usize, +} + +pub struct OccupiedEntry<'a, K: 'a + Storable + Ord + Clone, V: 'a + Storable, M: Memory> { + pub(crate) map: &'a mut BTreeMap, + pub(crate) key: K, + pub(crate) node: Node, + pub(crate) idx: usize, +} + +impl<'a, K: 'a + Storable + Ord + Clone, V: 'a + Storable, M: Memory> Entry<'a, K, V, M> { + pub fn key(&self) -> &K { + match self { + Entry::Occupied(entry) => entry.key(), + Entry::Vacant(entry) => entry.key(), + } + } + + pub fn into_key(self) -> K { + match self { + Entry::Occupied(entry) => entry.into_key(), + Entry::Vacant(entry) => entry.into_key(), + } + } +} + +impl<'a, K: 'a + Storable + Ord + Clone, V: 'a + Storable, M: Memory> VacantEntry<'a, K, V, M> { + pub fn key(&self) -> &K { + &self.key + } + + pub fn into_key(self) -> K { + self.key + } + + pub fn insert(mut self, value: V) { + self.node + .insert_entry(self.idx, (self.key, value.into_bytes_checked())); + + self.map.save_node(&mut self.node); + self.map.length += 1; + self.map.save_header(); + } +} + +impl<'a, K: 'a + Storable + Ord + Clone, V: 'a + Storable, M: Memory> OccupiedEntry<'a, K, V, M> { + pub fn key(&self) -> &K { + &self.key + } + + pub fn into_key(self) -> K { + self.key + } + + pub fn get(&self) -> V { + let value_bytes = self.node.value(self.idx, self.map.memory()); + V::from_bytes(Cow::Borrowed(value_bytes)) + } + + pub fn insert(mut self, value: V) { + self.map + .update_value(&mut self.node, self.idx, value.into_bytes_checked()); + } + + pub fn remove(self) { + match self.node.node_type() { + NodeType::Leaf if self.node.can_remove_entry_without_merging() => { + self.map.remove_from_leaf_node(self.node, self.idx); + } + NodeType::Leaf => { + // TODO avoid using this slow path + let root = self.map.load_node(self.map.root_addr); + self.map.remove_helper(root, &self.key); + } + NodeType::Internal => { + self.map + .remove_from_internal_node(self.node, self.idx, &self.key); + } + }; + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::cell::RefCell; + use std::rc::Rc; + + #[test] + fn entry_end_to_end() { + let mut map = BTreeMap::new(Rc::new(RefCell::new(Vec::new()))); + + for i in 0u32..100 { + let Entry::Vacant(e) = map.entry(i) else { + panic!(); + }; + e.insert(i); + } + + for i in 0u32..100 { + let Entry::Occupied(e) = map.entry(i) else { + panic!(); + }; + assert_eq!(i, e.get()); + e.insert(i + 1); + } + + for i in 0u32..100 { + let Entry::Occupied(e) = map.entry(i) else { + panic!(); + }; + assert_eq!(i + 1, e.get()); + e.remove(); + } + + assert!(map.is_empty()); + } +} diff --git a/src/btreemap/proptests.rs b/src/btreemap/proptests.rs index 4403a42b..2103b7fb 100644 --- a/src/btreemap/proptests.rs +++ b/src/btreemap/proptests.rs @@ -1,3 +1,4 @@ +use crate::btreemap::entry::Entry; use crate::{ btreemap::{ test::{b, make_memory, run_btree_test}, @@ -9,7 +10,8 @@ use crate::{ use proptest::collection::btree_set as pset; use proptest::collection::vec as pvec; use proptest::prelude::*; -use std::collections::{BTreeMap as StdBTreeMap, BTreeSet}; +use std::collections::{btree_map, BTreeMap as StdBTreeMap, BTreeSet}; +use std::ops::BitXor; use test_strategy::proptest; #[derive(Debug, Clone)] @@ -21,6 +23,7 @@ enum Operation { Values { from: usize, len: usize }, Get(usize), Remove(usize), + EntryInsertOrXor { key: Vec, value: Vec }, Range { from: usize, len: usize }, PopLast, PopFirst, @@ -43,6 +46,8 @@ fn operation_strategy() -> impl Strategy { .prop_map(|(from, len)| Operation::Values { from, len }), 50 => (any::()).prop_map(Operation::Get), 15 => (any::()).prop_map(Operation::Remove), + 10 => (any::>(), any::>()) + .prop_map(|(key, value)| Operation::EntryInsertOrXor { key, value }), 5 => (any::(), any::()) .prop_map(|(from, len)| Operation::Range { from, len }), 2 => Just(Operation::PopFirst), @@ -192,6 +197,58 @@ fn no_memory_leaks(#[strategy(pvec(pvec(0..u8::MAX, 100..10_000), 100))] keys: V assert_eq!(btree.allocator.num_allocated_chunks(), 0); } +#[proptest] +fn entry( + #[strategy(pvec(0..255u8, 10))] keys: Vec, + #[strategy(pvec(0..3u8, 10))] operations: Vec, +) { + run_btree_test(|mut btree| { + let mut std_map = StdBTreeMap::new(); + + // Operations (if Occupied): + // 0 - insert + // 1 - increment + // 2 - remove + // + // Operations (if Vacant): + // - always insert + for (key, operation) in keys.iter().copied().zip(operations.iter().copied()) { + let entry = btree.entry(key); + let std_entry = std_map.entry(key); + match (entry, std_entry) { + (Entry::Occupied(e), btree_map::Entry::Occupied(mut std_e)) => match operation { + 0 => { + e.insert(key); + std_e.insert(key); + } + 1 => { + let existing = e.get(); + let std_existing = *std_e.get(); + e.insert(existing.overflowing_add(1).0); + std_e.insert(std_existing.overflowing_add(1).0); + } + 2 => { + e.remove(); + std_e.remove(); + } + _ => unreachable!(), + }, + (Entry::Vacant(e), btree_map::Entry::Vacant(std_e)) => { + e.insert(key); + std_e.insert(key); + } + _ => prop_assert!(false, "Map out of sync with std lib BTreeMap"), + } + } + + let entries: Vec<_> = btree.iter().map(|e| (*e.key(), e.value())).collect(); + let std_entries: Vec<_> = std_map.into_iter().collect(); + + prop_assert_eq!(entries, std_entries); + Ok(()) + }); +} + // Given an operation, executes it on the given stable btreemap and standard btreemap, verifying // that the result of the operation is equal in both btrees. fn execute_operation( @@ -320,6 +377,40 @@ fn execute_operation( assert_eq!(btree.remove(&k), Some(v)); } } + Operation::EntryInsertOrXor { key, value } => { + match std_btree.entry(key.clone()) { + btree_map::Entry::Occupied(mut entry) => { + let existing = entry.get().clone(); + let xor = existing + .into_iter() + .zip(value.clone()) + .map(|(l, r)| l.bitxor(r)) + .collect::>(); + entry.insert(xor); + } + btree_map::Entry::Vacant(entry) => { + entry.insert(value.clone()); + } + }; + + match btree.entry(key.clone()) { + Entry::Occupied(entry) => { + let existing = entry.get(); + let xor = existing + .into_iter() + .zip(value.clone()) + .map(|(l, r)| l.bitxor(r)) + .collect::>(); + entry.insert(xor); + } + Entry::Vacant(entry) => { + entry.insert(value.clone()); + } + } + + assert_eq!(btree.get(&key).as_ref(), std_btree.get(&key)); + } + Operation::Range { from, len } => { assert_eq!(std_btree.len(), btree.len() as usize); if std_btree.is_empty() {