From ed05330b7eddbd7a3b166767c3d890df0692a67d Mon Sep 17 00:00:00 2001 From: Declan Kelly Date: Sun, 7 Jul 2024 19:13:56 -0700 Subject: [PATCH] Rework the tree_viz example to only read from file **Description** - Slim down the tree viz example so it only reads from file and does not generate trees - Modify the existing example data so that it has a single delimiter with keys and values **Motivation** The tree generation part is less useful and I felt like making this example do only a single thing well was a better use-case. **Testing Done** Ran it for a subset of the dictionary file. --- examples/example-data/medium_tree.txt | 14 +- .../medium_tree_delete_without_merge.txt | 12 +- .../example-data/small_tree_with_prefix.txt | 8 +- ...l_tree_with_prefix_delete_fixup_prefix.txt | 6 +- ...l_tree_with_prefix_delete_pull_up_root.txt | 6 +- ...with_prefix_delete_pull_up_single_node.txt | 6 +- examples/tree_viz.rs | 273 +++++------------- src/nodes/visitor/pretty_printer.rs | 46 ++- src/tests_common.rs | 9 +- 9 files changed, 130 insertions(+), 250 deletions(-) diff --git a/examples/example-data/medium_tree.txt b/examples/example-data/medium_tree.txt index a8de3c18..172a655c 100644 --- a/examples/example-data/medium_tree.txt +++ b/examples/example-data/medium_tree.txt @@ -1,7 +1,7 @@ -1,2,3,4,5,6,A -2,4,6,8,10,12,B -1,2,3,4,7,8,C -1,2,3,4,5,9,D -2,4,6,8,10,14,E -2,4,6,8,10,16,F -2,4,6,8,10,18,G \ No newline at end of file +123456|A +24681012|B +123478|C +123459|D +24681014|E +24681016|F +24681018|G diff --git a/examples/example-data/medium_tree_delete_without_merge.txt b/examples/example-data/medium_tree_delete_without_merge.txt index ad0b7060..5ba1d456 100644 --- a/examples/example-data/medium_tree_delete_without_merge.txt +++ b/examples/example-data/medium_tree_delete_without_merge.txt @@ -1,6 +1,6 @@ -1,2,3,4,5,6,A -2,4,6,8,10,12,B -1,2,3,4,7,8,C -1,2,3,4,5,9,D -2,4,6,8,10,14,E -2,4,6,8,10,18,G \ No newline at end of file +123456|A +24681012|B +123478|C +123459|D +24681014|E +24681018|G diff --git a/examples/example-data/small_tree_with_prefix.txt b/examples/example-data/small_tree_with_prefix.txt index 23f8a05f..5b80edf8 100644 --- a/examples/example-data/small_tree_with_prefix.txt +++ b/examples/example-data/small_tree_with_prefix.txt @@ -1,4 +1,4 @@ -1,2,3,4,5,6,A -2,4,6,8,10,12,B -1,2,3,4,7,8,C -1,2,3,4,5,9,D \ No newline at end of file +123456|A +24681012|B +123478|C +123459|D diff --git a/examples/example-data/small_tree_with_prefix_delete_fixup_prefix.txt b/examples/example-data/small_tree_with_prefix_delete_fixup_prefix.txt index 074f77c7..364ea8e0 100644 --- a/examples/example-data/small_tree_with_prefix_delete_fixup_prefix.txt +++ b/examples/example-data/small_tree_with_prefix_delete_fixup_prefix.txt @@ -1,3 +1,3 @@ -1,2,3,4,5,6,A -2,4,6,8,10,12,B -1,2,3,4,5,9,D \ No newline at end of file +123456|A +24681012|B +123459|D diff --git a/examples/example-data/small_tree_with_prefix_delete_pull_up_root.txt b/examples/example-data/small_tree_with_prefix_delete_pull_up_root.txt index 8f44db78..d291cb50 100644 --- a/examples/example-data/small_tree_with_prefix_delete_pull_up_root.txt +++ b/examples/example-data/small_tree_with_prefix_delete_pull_up_root.txt @@ -1,3 +1,3 @@ -1,2,3,4,5,6,A -1,2,3,4,7,8,C -1,2,3,4,5,9,D \ No newline at end of file +123456|A +123478|C +123459|D diff --git a/examples/example-data/small_tree_with_prefix_delete_pull_up_single_node.txt b/examples/example-data/small_tree_with_prefix_delete_pull_up_single_node.txt index d1d9b3b6..ff54f45a 100644 --- a/examples/example-data/small_tree_with_prefix_delete_pull_up_single_node.txt +++ b/examples/example-data/small_tree_with_prefix_delete_pull_up_single_node.txt @@ -1,3 +1,3 @@ -1,2,3,4,5,6,A -2,4,6,8,10,12,B -1,2,3,4,7,8,C \ No newline at end of file +123456|A +24681012|B +123478|C diff --git a/examples/tree_viz.rs b/examples/tree_viz.rs index d430deb1..dc5fd110 100644 --- a/examples/tree_viz.rs +++ b/examples/tree_viz.rs @@ -1,32 +1,27 @@ use argh::FromArgs; use blart::{ visitor::{DotPrinter, DotPrinterSettings}, - TreeMap, + AsBytes, NoPrefixesBytes, TreeMap, }; use std::{ error::Error, + ffi::CString, fmt::Display, - fs::{File, OpenOptions}, + fs::OpenOptions, io::{self, BufRead, BufReader, BufWriter, Write}, - iter, - path::PathBuf, - str::FromStr, + path::{Path, PathBuf}, }; #[derive(FromArgs)] /// TREES struct TreeToDotArgs { - /// input to read keys from an external file + /// optional delimiter to split key from value on each file line #[argh(option)] - input_file: Option, - - /// what shape of tree to generate - #[argh(positional)] - shape: TreeShape, + delimiter: Option, - /// how large the tree should be + /// input to read keys from an external file #[argh(positional)] - size: usize, + input_file: PathBuf, /// where to output the tree diagram /// @@ -35,17 +30,17 @@ struct TreeToDotArgs { output_location: String, } -fn main() -> Result<(), Box> { +fn main() { let args: TreeToDotArgs = argh::from_env(); let mut tree = TreeMap::new(); - for (key, value) in args.shape.generate_keys(args.size, args.input_file) { - let _ = tree.try_insert(key, value).unwrap(); + for (key, value) in read_key_values_from_text_file(&args.input_file, args.delimiter) { + let _ = tree.insert(key, value); } if tree.is_empty() { - return Err(Box::new(EmptyTreeError)); + panic!("Tree should now be empty after reading keys from file"); }; if args.output_location == "_" { @@ -54,26 +49,43 @@ fn main() -> Result<(), Box> { let mut buffer = BufWriter::new(handle); - write_tree(&mut buffer, tree)?; + write_tree(&mut buffer, tree) } else { let file = OpenOptions::new() .read(true) .write(true) .create(true) .truncate(true) - .open(args.output_location)?; + .open(args.output_location) + .expect("Failed to open file for output"); let mut buffer = BufWriter::new(file); - write_tree(&mut buffer, tree)?; + write_tree(&mut buffer, tree) } + .expect("Failed to write tree to output") +} - Ok(()) +pub struct DisplayWrapper(CString); + +impl Display for DisplayWrapper { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0.to_string_lossy()) + } +} + +impl AsBytes for DisplayWrapper { + fn as_bytes(&self) -> &[u8] { + ::as_bytes(&self.0) + } } +// SAFETY: We can impl `NoPrefixesBytes` since `CString` also implements that +unsafe impl NoPrefixesBytes for DisplayWrapper where CString: NoPrefixesBytes {} + fn write_tree( output: &mut dyn Write, - tree: TreeMap, String>, + tree: TreeMap, ) -> Result<(), Box> { DotPrinter::print( output, @@ -87,193 +99,38 @@ fn write_tree( Ok(()) } -#[derive(Debug)] -enum TreeShape { - LeftSkew, - FullNode4, - FullNode16, - FullNode48, - FullNode256, - FromTextFile, -} - -impl TreeShape { - fn generate_keys( - self, - tree_size: usize, - text_file_path: Option, - ) -> Box, String)>> { - match self { - TreeShape::LeftSkew => Box::new(TreeShape::generate_left_skew_keys(tree_size)), - TreeShape::FullNode4 => Box::new(TreeShape::generate_full_keys(tree_size, 4)), - TreeShape::FullNode16 => Box::new(TreeShape::generate_full_keys(tree_size, 16)), - TreeShape::FullNode48 => Box::new(TreeShape::generate_full_keys(tree_size, 48)), - TreeShape::FullNode256 => Box::new(TreeShape::generate_full_keys(tree_size, 256)), - TreeShape::FromTextFile => { - let text_file = OpenOptions::new() - .read(true) - .open( - text_file_path - .expect("file path not passed to 'from_text_file' tree shape"), - ) - .expect("unable to open text file"); - Box::new(TreeShape::read_key_values_from_text_file(text_file)) - }, - } - } - - fn read_key_values_from_text_file( - text_file: File, - ) -> impl Iterator, String)> { - BufReader::new(text_file).lines().map(|line| { - let line = line.expect("unable to read line"); - let entry_components = line.split(',').collect::>(); - - let key = entry_components[..entry_components.len() - 1] - .iter() - .map(|num| u8::from_str(num.trim())) - .collect::, _>>() - .expect("unable to parse bytes"); - let value = String::from( - entry_components - .last() - .copied() - .expect("expected at least one component in line"), +fn read_key_values_from_text_file( + text_file_path: &Path, + delimiter: Option, +) -> impl Iterator { + let text_file = OpenOptions::new() + .read(true) + .open(text_file_path) + .expect("unable to open text file"); + + BufReader::new(text_file).lines().map(move |line| { + let line = line.expect("unable to read line"); + if let Some(delimiter) = delimiter { + let entry_components = line.split(delimiter).collect::>(); + + assert_eq!( + entry_components.len(), + 2, + "Each line must only have delimiter once" ); - (key, value) - }) - } - - fn generate_left_skew_keys(tree_size: usize) -> impl Iterator, String)> { - (0..tree_size) - .map(|key_size| { - (0..key_size) - .map(|_| 1u8) - .chain(iter::once(u8::MAX)) - .collect::>() - .into_boxed_slice() - }) - .enumerate() - .map(|(value, key)| (key, value.to_string())) - } - - fn generate_full_keys( - tree_height: usize, - node_width: usize, - ) -> impl Iterator, String)> { - // tree size will be interpreted as the number of levels of all - // InnerNode{node_width} with a last layer of leaves - // - // Assuming node_width = 4 - // 1 level - 4 leaves - // 0,255:0|1,255:1|2,255:2|3,255:3 - // 2 levels - 16 leaves - // 0,0,255:0|0,1,255:1|0,2,255:2|0,3,255:3 - // 1,0,255:0|1,1,255:1|1,2,255:2|1,3,255:3 - // 2,0,255:0|2,1,255:1|2,2,255:2|2,3,255:3 - // 3,0,255:0|3,1,255:1|3,2,255:2|3,3,255:3 - // 3 levels - 64 leaves - - // [0-3],[0-3],...,[0-3],256 - // \---- n numbers ----/ - // total key size is n + 1 - - struct FullKeysIter { - tree_height: usize, - node_width: usize, - digit_stack: Vec, - } - - impl Iterator for FullKeysIter { - type Item = Box<[u8]>; - - fn next(&mut self) -> Option { - if self.digit_stack.is_empty() { - return None; - } - - let mut new_key = self.digit_stack.clone(); - new_key.push(u8::MAX); - let new_key = new_key.into_boxed_slice(); - - // update the stack for next value - - for digit_idx in (0..self.tree_height).rev() { - if let Some(updated_digit) = self.digit_stack[digit_idx].checked_add(1) { - self.digit_stack[digit_idx] = updated_digit - } else { - // At 256, max width - self.digit_stack.pop(); - continue; - } - - if usize::from(self.digit_stack[digit_idx]) >= self.node_width { - self.digit_stack.pop(); - } else { - // under limit - break; - } - } - - if !self.digit_stack.is_empty() { - while self.digit_stack.len() < self.tree_height { - self.digit_stack.push(0); - } - } - - Some(new_key) - } - } - - Box::new( - FullKeysIter { - tree_height, - node_width, - digit_stack: vec![0; tree_height], - } - .enumerate() - .map(|(value, key)| (key, value.to_string())), - ) - } -} - -impl FromStr for TreeShape { - type Err = ShapeParseError; - - fn from_str(s: &str) -> Result { - match s { - "left_skew" => Ok(TreeShape::LeftSkew), - "full_node4" => Ok(TreeShape::FullNode4), - "full_node16" => Ok(TreeShape::FullNode16), - "full_node48" => Ok(TreeShape::FullNode48), - "full_node256" => Ok(TreeShape::FullNode256), - "from_text_file" => Ok(TreeShape::FromTextFile), - _ => Err(ShapeParseError(s.into())), + let key = entry_components[0]; + let value = entry_components[1]; + + ( + DisplayWrapper(CString::new(String::from(key).into_bytes()).unwrap()), + value.into(), + ) + } else { + ( + DisplayWrapper(CString::new(line.into_bytes()).unwrap()), + String::new(), + ) } - } -} - -#[derive(Debug)] -struct ShapeParseError(String); - -impl Display for ShapeParseError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "Unable to parse tree shape from argument value [{}].", - self.0 - ) - } + }) } - -#[derive(Debug)] -struct EmptyTreeError; - -impl Display for EmptyTreeError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "There were no keys to insert into the tree!") - } -} - -impl Error for EmptyTreeError {} diff --git a/src/nodes/visitor/pretty_printer.rs b/src/nodes/visitor/pretty_printer.rs index 1e6e1788..3ce6b54c 100644 --- a/src/nodes/visitor/pretty_printer.rs +++ b/src/nodes/visitor/pretty_printer.rs @@ -3,7 +3,7 @@ use crate::{ AsBytes, InnerNode, NodeType, OpaqueNodePtr, TreeMap, }; use std::{ - fmt::Debug, + fmt::{Debug, Display}, io::{self, Write}, }; @@ -32,8 +32,8 @@ impl DotPrinter { settings: DotPrinterSettings, ) -> Option> where - K: Debug + AsBytes, - V: Debug, + K: Display + AsBytes, + V: Display, { tree.root.map(|root| { // SAFETY: Since we get a reference to the `TreeMap`, we know the @@ -53,8 +53,8 @@ impl DotPrinter { settings: DotPrinterSettings, ) -> io::Result<()> where - K: Debug + AsBytes, - V: Debug, + K: Display + AsBytes, + V: Display, { let mut visitor = DotPrinter { output, @@ -87,8 +87,8 @@ impl DotPrinter { inner_node: &N, ) -> io::Result where - K: Debug + AsBytes, - T: Debug, + K: Display + AsBytes, + T: Display, N: InnerNode, { let header = inner_node.header(); @@ -144,8 +144,8 @@ impl DotPrinter { impl Visitor for DotPrinter where - K: Debug + AsBytes, - T: Debug, + K: Display + AsBytes, + T: Display, O: Write, { type Output = io::Result; @@ -182,7 +182,7 @@ where if self.settings.display_node_address { writeln!( self.output, - "{{ {:p}}} | {{{:?}}} | {{{:?}}} | {{{:?}}}}}\"]", + "{{ {:p}}} | {{{:?}}} | {{{}}} | {{{}}}}}\"]", t as *const _, NodeType::Leaf, t.key_ref(), @@ -191,7 +191,7 @@ where } else { writeln!( self.output, - "{{ {:?}}} | {{{:?}}} | {{{:?}}}}}\"]", + "{{ {:?}}} | {{{}}} | {{{}}}}}\"]", NodeType::Leaf, t.key_ref(), t.value_ref() @@ -210,11 +210,31 @@ mod tests { #[test] fn simple_tree_output_to_dot() { - let root: OpaqueNodePtr, usize, 16> = + struct DisplayAsDebug(T); + + impl Display for DisplayAsDebug + where + T: Debug, + { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + ::fmt(&self.0, f) + } + } + + impl AsBytes for DisplayAsDebug + where + T: AsBytes, + { + fn as_bytes(&self) -> &[u8] { + self.0.as_bytes() + } + } + + let root: OpaqueNodePtr>, usize, 16> = crate::tests_common::setup_tree_from_entries( crate::tests_common::generate_key_fixed_length([3, 3]) .enumerate() - .map(|(a, b)| (b, a)), + .map(|(a, b)| (DisplayAsDebug(b), a)), ); let mut buffer = Vec::new(); diff --git a/src/tests_common.rs b/src/tests_common.rs index 3e8f6a06..b3414ea9 100644 --- a/src/tests_common.rs +++ b/src/tests_common.rs @@ -332,9 +332,12 @@ where } #[allow(dead_code)] -pub(crate) fn setup_tree_from_entries( - mut entries_it: impl Iterator, V)>, -) -> OpaqueNodePtr, V, PREFIX_LEN> { +pub(crate) fn setup_tree_from_entries( + mut entries_it: impl Iterator, +) -> OpaqueNodePtr +where + K: AsBytes, +{ use crate::{LeafNode, NodePtr}; let (first_key, first_value) = entries_it.next().unwrap();