Mercurial > lbo > hg > btreex
changeset 4:48536f1240fc
More tests, check coverage
author | Lewin Bormann <lbo@spheniscida.de> |
---|---|
date | Tue, 05 Jul 2022 09:40:05 -0700 |
parents | a94eac64aee8 |
children | b4656ce03bda |
files | .hgignore src/lib.rs |
diffstat | 2 files changed, 100 insertions(+), 20 deletions(-) [+] |
line wrap: on
line diff
--- a/.hgignore Tue Jul 05 08:09:38 2022 -0700 +++ b/.hgignore Tue Jul 05 09:40:05 2022 -0700 @@ -1,2 +1,4 @@ ^target/ ^Cargo.lock$ +^kcov-out +callgrind.*
--- a/src/lib.rs Tue Jul 05 08:09:38 2022 -0700 +++ b/src/lib.rs Tue Jul 05 09:40:05 2022 -0700 @@ -19,20 +19,36 @@ } } +trait Compare<T> { + fn cmp(v1: &T, v2: &T) -> Ordering; +} + +struct StandardCompare; + +impl<T: Ord> Compare<T> for StandardCompare { + fn cmp(v1: &T, v2: &T) -> Ordering { + v1.cmp(v2) + } +} + #[derive(Debug)] -struct BTree<T> { +struct BTree<T, Cmp: Compare<T>> { v: Vec<Node<T>>, root: usize, + cmp: std::marker::PhantomData<Cmp>, } -impl<T: std::fmt::Debug + Ord> BTree<T> { - fn new(initial_capacity: usize) -> BTree<T> { +impl<T: std::fmt::Debug + Ord> BTree<T, StandardCompare> { + fn new(initial_capacity: usize) -> BTree<T, StandardCompare> { BTree { v: Vec::with_capacity(initial_capacity), root: 0, + cmp: Default::default(), } } +} +impl<T: std::fmt::Debug + Ord, Cmp: Compare<T>> BTree<T, Cmp> { fn free(&self, node: usize) -> usize { self.v[node] .entries @@ -73,14 +89,37 @@ s } - fn find<'a>(&'a self, value: &T) -> &'a T { - panic!(); + pub fn find<'a>(&'a self, value: &T) -> Option<&'a T> { + self.find_from(self.root, value) } - fn find_from<'a>(&'a self, node: usize, value: &T) -> &'a T { + fn find_from<'a>(&'a self, node: usize, value: &T) -> Option<&'a T> { + let mut j = 0; + // Break once we arrived at the entry right of the correct child, if we don't return + // earlier. for i in 0..ORDER { - if i < ORDER - 1 {} + if i < ORDER - 1 { + if let Some(el) = self.get(node, i) { + match Cmp::cmp(value, el) { + Ordering::Less => { + j = i; + break; + } + Ordering::Equal => return Some(el), + Ordering::Greater => continue, + } + } else { + j = i; + break; + } + } else { + j = i; + break; + } } - panic!(); + if let Some(ch) = self.get_child(node, j) { + return self.find_from(*ch, value); + } + None } /// Insert a value into an existing node, preserving the order. @@ -98,7 +137,7 @@ let node = &mut self.v[node]; for i in 0..ORDER - 1 { if let Some(ref el) = node.entries[i] { - if value.cmp(el) == Ordering::Less { + if Cmp::cmp(&value, el) == Ordering::Less { // Found right location. Shift remaining elements. loc = i; break; @@ -146,11 +185,13 @@ (leftix, pivot.unwrap(), rightix) } + /// Returns true if the given node is a leaf (doesn't have children). fn is_leaf(&self, node: usize) -> bool { self.v[node].children.iter().all(|c| c.is_none()) } - /// Insert & root splitting. - fn insert(&mut self, value: T) -> bool { + + /// Insert a value into the BTree. + pub fn insert(&mut self, value: T) -> bool { if let Some((l, pivot, r)) = self.insert_into(self.root, value) { // Split root let mut newroot = Node::new(); @@ -181,26 +222,26 @@ self.insert_inline(node, value, None, None); return None; } else if self.is_leaf(node) && self.free(node) < 1 { - // Leaf node but needs to be split. + // Leaf node but full - needs to be split. let (l, pivot, r) = self.split(node); - // Full leaf node? // Insert value into left or right subtree. - match value.cmp(&pivot) { + match Cmp::cmp(&value, &pivot) { Ordering::Less => assert!(self.insert_into(l, value).is_none()), Ordering::Greater => assert!(self.insert_into(r, value).is_none()), _ => panic!("Attempting to insert duplicate element"), }; + // Return fragments upwards to be inserted. return Some((l, pivot, r)); } else { // Descend into node. let mut split = None; - // Find correct child. + // Find correct child to descend into. for i in 0..ORDER { if i < ORDER - 1 { if let Some(el) = self.get(node, i) { - match value.cmp(el) { + match Cmp::cmp(&value, el) { Ordering::Less => { split = self.insert_into(self.get_child(node, i).unwrap(), value); break; @@ -228,7 +269,7 @@ let (l, pivot, r) = self.split(node); // Insert value into left or right subtree. - match value.cmp(&pivot) { + match Cmp::cmp(&value, &pivot) { Ordering::Less => assert!(self.insert_into(l, value).is_none()), Ordering::Greater => assert!(self.insert_into(r, value).is_none()), _ => panic!("Attempting to insert duplicate element"), @@ -261,7 +302,7 @@ } } } -impl<T: std::fmt::Debug> BTree<T> { +impl<T: std::fmt::Debug, Cmp: Compare<T>> BTree<T, Cmp> { fn debug_print(&self) -> String { let mut t = TreeBuilder::new(format!("<{}>", self.root)); self.print_subtree(&mut t, self.root); @@ -289,7 +330,7 @@ use rand::{rngs::StdRng, RngCore, SeedableRng}; #[test] fn basic_test() { - let mut bt = BTree::<i32>::new(16); + let mut bt = BTree::new(16); let ns = &[ 15, 2, 21, 35, 4, 62, 31, 27, 88, 1, 12, 45, 11, 16, 23, 20, 22, 24, 25, 26, 28, 29, @@ -311,7 +352,7 @@ for seed in &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] { let mut rng = StdRng::seed_from_u64(*seed); let N = 1000; - let mut bt = BTree::<u32>::new(N / 2); + let mut bt = BTree::new(N / 2); for i in 0..N { let rn = rng.next_u32(); bt.insert(rn); @@ -321,6 +362,43 @@ } assert_eq!(i + 1, bt.count()); } + println!("{}", bt.debug_print()); + } + } + + #[test] + fn test_find() { + time_test::time_test!("1000 x insert/find"); + let mut rng = StdRng::seed_from_u64(1); + let N = 1000; + let mut items = Vec::with_capacity(N); + let mut bt = BTree::new(N / 3); + for i in 0..N { + let rn = rng.next_u32(); + bt.insert(rn); + items.push(rn); + + for rn in items.iter() { + assert_eq!(bt.find(rn), Some(rn)); + } + } + + for (i, rn) in items.iter().enumerate() { + assert_eq!(bt.find(rn), Some(rn)); + } + } + + #[test] + fn test_find_sequential() { + time_test::time_test!("1000 x insert/find sequential"); + let N = 1000; + let mut bt = BTree::new(N / 3); + for i in 0..N { + bt.insert(i); + } + + for i in 0..N { + assert_eq!(bt.find(&i), Some(&i)); } }