diff --git a/lab4/zad1/.gitignore b/lab4/zad1/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/lab4/zad1/.gitignore @@ -0,0 +1 @@ +target diff --git a/lab4/zad1/Cargo.lock b/lab4/zad1/Cargo.lock new file mode 100644 index 0000000..938d426 --- /dev/null +++ b/lab4/zad1/Cargo.lock @@ -0,0 +1,87 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "libgen" +version = "0.1.0" +dependencies = [ + "rand", +] + +[[package]] +name = "libtree" +version = "0.1.0" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "zad1" +version = "0.1.0" +dependencies = [ + "libgen", + "libtree", +] diff --git a/lab4/zad1/Cargo.toml b/lab4/zad1/Cargo.toml new file mode 100644 index 0000000..53639ff --- /dev/null +++ b/lab4/zad1/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "zad1" +version = "0.1.0" +edition = "2021" + +[dependencies] +libgen = { path = "../../libgen" } +libtree = { path = "../../libtree" } diff --git a/lab4/zad1/src/main.rs b/lab4/zad1/src/main.rs new file mode 100644 index 0000000..1ee4939 --- /dev/null +++ b/lab4/zad1/src/main.rs @@ -0,0 +1,34 @@ +use libgen::{gen_asc, gen_rand}; +use libtree::BinarySearchTree; + +fn main() { + println!("1. insert 50 ascending; delete 50 random"); + let mut tree1 = BinarySearchTree::new(50); + + for i in gen_asc(50) { + println!("insert {i}"); + tree1.insert(i); + println!("{}", tree1); + } + + for i in gen_rand(50) { + println!("delete {i}"); + tree1.delete(i); + println!("{}", tree1); + } + + println!("2. insert 50 random; delete 50 random"); + let mut tree2 = BinarySearchTree::new(50); + + for i in gen_rand(50) { + println!("insert {i}"); + tree2.insert(i); + println!("{}", tree2); + } + + for i in gen_rand(50) { + println!("delete {i}"); + tree2.delete(i); + println!("{}", tree2); + } +} diff --git a/lab4/zad2/.gitignore b/lab4/zad2/.gitignore new file mode 100644 index 0000000..e2abe1e --- /dev/null +++ b/lab4/zad2/.gitignore @@ -0,0 +1,2 @@ +target +results diff --git a/lab4/zad2/Cargo.lock b/lab4/zad2/Cargo.lock new file mode 100644 index 0000000..dd3b725 --- /dev/null +++ b/lab4/zad2/Cargo.lock @@ -0,0 +1,285 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "either" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "libgen" +version = "0.1.0" +dependencies = [ + "rand", +] + +[[package]] +name = "libtree" +version = "0.1.0" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +dependencies = [ + "bitflags", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "zad2" +version = "0.1.0" +dependencies = [ + "dashmap", + "libgen", + "libtree", + "rayon", +] diff --git a/lab4/zad2/Cargo.toml b/lab4/zad2/Cargo.toml new file mode 100644 index 0000000..ead673e --- /dev/null +++ b/lab4/zad2/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "zad2" +version = "0.1.0" +edition = "2021" + +[dependencies] +libtree = { path = "../../libtree" } +libgen = { path = "../../libgen" } +rayon = "1.10.0" +dashmap = "5.5.3" diff --git a/lab4/zad2/src/main.rs b/lab4/zad2/src/main.rs new file mode 100644 index 0000000..c9394da --- /dev/null +++ b/lab4/zad2/src/main.rs @@ -0,0 +1,277 @@ +use std::{fs::{self, OpenOptions}, io::{self, Write}, sync::Arc}; + +use dashmap::DashMap; +use libgen::{gen_asc, gen_rand}; +use rayon::{current_thread_index, iter::{IntoParallelIterator, ParallelIterator}}; +use libtree::{BinarySearchTree, Stats}; + +fn comp_avg(results_map: &DashMap>) -> DashMap { + results_map.iter() + .map(|ref_multi| { + let (i, results) = ref_multi.pair(); + (*i, (results.iter() + .map(|res| res.comparisons) + .sum::() as f64 / results.len() as f64)) + }) + .collect() +} + +fn comp_max(results_map: &DashMap>) -> DashMap { + results_map.iter() + .map(|ref_multi| { + let (i, results) = ref_multi.pair(); + (*i, results.iter() + .map(|s| s.comparisons) + .max() + .unwrap()) + }) + .collect() +} + +fn read_avg(results_map: &DashMap>) -> DashMap { + results_map.iter() + .map(|ref_multi| { + let (i, results) = ref_multi.pair(); + (*i, (results.iter() + .map(|res| res.reads) + .sum::() as f64 / results.len() as f64)) + }) + .collect() +} + +fn read_max(results_map: &DashMap>) -> DashMap { + results_map.iter() + .map(|ref_multi| { + let (i, results) = ref_multi.pair(); + (*i, results.iter() + .map(|s| s.reads) + .max() + .unwrap()) + }) + .collect() +} + +fn write_avg(results_map: &DashMap>) -> DashMap { + results_map.iter() + .map(|ref_multi| { + let (i, results) = ref_multi.pair(); + (*i, (results.iter() + .map(|res| res.writes) + .sum::() as f64 / results.len() as f64)) + }) + .collect() +} + +fn write_max(results_map: &DashMap>) -> DashMap { + results_map.iter() + .map(|ref_multi| { + let (i, results) = ref_multi.pair(); + (*i, results.iter() + .map(|s| s.writes) + .max() + .unwrap()) + }) + .collect() +} + +fn main() -> io::Result<()> { + + let m = 20; + + let asc_results: Arc>> = Arc::new(DashMap::new()); + let asc_heights: Arc>> = Arc::new(DashMap::new()); + let rand_results: Arc>> = Arc::new(DashMap::new()); + let rand_heights: Arc>> = Arc::new(DashMap::new()); + + let asc_results_clone = Arc::clone(&asc_results); + let asc_heights_clone = Arc::clone(&asc_heights); + let rand_results_clone = Arc::clone(&rand_results); + let rand_heights_clone = Arc::clone(&rand_heights); + + (10000u64..=100000u64).step_by(10000).collect::>().into_par_iter().for_each(move |n| { + if asc_results_clone.get(&n).is_some() { + return; + } + if asc_heights_clone.get(&n).is_some() { + return; + } + if rand_results_clone.get(&n).is_some() { + return; + } + if rand_heights_clone.get(&n).is_some() { + return; + } + println!("{}: starting n: {n}", current_thread_index().unwrap()); + + asc_results_clone.insert(n, vec![]); + asc_heights_clone.insert(n, vec![]); + rand_results_clone.insert(n, vec![]); + rand_heights_clone.insert(n, vec![]); + + let mut asc = BinarySearchTree::new(n as usize); + let mut rand = BinarySearchTree::new(n as usize); + + for _ in 0..m { + for i in gen_asc(n) { + asc.insert(i); + asc_heights_clone.get_mut(&n).unwrap().push(asc.height()); + } + for i in gen_rand(n) { + asc.delete(i); + asc_heights_clone.get_mut(&n).unwrap().push(asc.height()); + } + + asc_results_clone.get_mut(&n).unwrap().push(asc.stats.clone()); + + for i in gen_rand(n) { + rand.insert(i); + rand_heights_clone.get_mut(&n).unwrap().push(rand.height()); + } + for i in gen_rand(n) { + rand.delete(i); + rand_heights_clone.get_mut(&n).unwrap().push(rand.height()); + } + + rand_results_clone.get_mut(&n).unwrap().push(rand.stats.clone()); + } + println!("{}: finished n: {n}", current_thread_index().unwrap()); + }); + + let asc_comp_averages = comp_avg(&asc_results); + let asc_read_averages = read_avg(&asc_results); + let asc_write_averages = write_avg(&asc_results); + + let asc_height_averages: DashMap<_, _> = asc_heights.iter() + .map(|ref_multi| { + let (i, results) = ref_multi.pair(); + (*i, results.iter().sum::() as f64 / results.len() as f64) + }) + .collect(); + + let asc_comp_max = comp_max(&asc_results); + let asc_read_max = read_max(&asc_results); + let asc_write_max = write_max(&asc_results); + + let asc_height_max: DashMap<_, _> = asc_heights.iter() + .map(|ref_multi| { + let (i, results) = ref_multi.pair(); + (*i, *results.iter().max().unwrap()) + }) + .collect(); + + let rand_comp_averages = comp_avg(&rand_results); + let rand_read_averages = read_avg(&rand_results); + let rand_write_averages = write_avg(&rand_results); + + let rand_height_averages: DashMap<_, _> = rand_heights.iter() + .map(|ref_multi| { + let (i, results) = ref_multi.pair(); + (*i, results.iter().sum::() as f64 / results.len() as f64) + }) + .collect(); + + let rand_comp_max = comp_max(&rand_results); + let rand_read_max = read_max(&rand_results); + let rand_write_max = write_max(&rand_results); + + let rand_height_max: DashMap<_, _> = rand_heights.iter() + .map(|ref_multi| { + let (i, results) = ref_multi.pair(); + (*i, *results.iter().max().unwrap()) + }) + .collect(); + + _ = fs::create_dir_all("./results"); + + let mut asc_comp_file = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open("./results/asc_comp")?; + + let mut asc_read_file = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open("./results/asc_read")?; + + let mut asc_write_file = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open("./results/asc_write")?; + + let mut asc_height_file = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open("./results/asc_height")?; + + let mut rand_comp_file = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open("./results/rand_comp")?; + + let mut rand_read_file = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open("./results/rand_read")?; + + let mut rand_write_file = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open("./results/rand_write")?; + + let mut rand_height_file = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open("./results/rand_height")?; + + for n in (10000u64..=100000u64).step_by(10000) { + writeln!(asc_comp_file, "{n} {} {}", + asc_comp_averages.get(&n).unwrap().value(), + asc_comp_max.get(&n).unwrap().value(), + )?; + + writeln!(asc_read_file, "{n} {} {}", + asc_read_averages.get(&n).unwrap().value(), + asc_read_max.get(&n).unwrap().value(), + )?; + + writeln!(asc_write_file, "{n} {} {}", + asc_write_averages.get(&n).unwrap().value(), + asc_write_max.get(&n).unwrap().value(), + )?; + + writeln!(asc_height_file, "{n} {} {}", + asc_height_averages.get(&n).unwrap().value(), + asc_height_max.get(&n).unwrap().value(), + )?; + + writeln!(rand_comp_file, "{n} {} {}", + rand_comp_averages.get(&n).unwrap().value(), + rand_comp_max.get(&n).unwrap().value(), + )?; + + writeln!(rand_read_file, "{n} {} {}", + rand_read_averages.get(&n).unwrap().value(), + rand_read_max.get(&n).unwrap().value(), + )?; + + writeln!(rand_write_file, "{n} {} {}", + rand_write_averages.get(&n).unwrap().value(), + rand_write_max.get(&n).unwrap().value(), + )?; + + writeln!(rand_height_file, "{n} {} {}", + rand_height_averages.get(&n).unwrap().value(), + rand_height_max.get(&n).unwrap().value(), + )?; + } + + Ok(()) +} diff --git a/libtree/.gitignore b/libtree/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/libtree/.gitignore @@ -0,0 +1 @@ +target diff --git a/libtree/Cargo.lock b/libtree/Cargo.lock new file mode 100644 index 0000000..2799e16 --- /dev/null +++ b/libtree/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "libtree" +version = "0.1.0" diff --git a/libtree/Cargo.toml b/libtree/Cargo.toml new file mode 100644 index 0000000..2d829b8 --- /dev/null +++ b/libtree/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "libtree" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/libtree/src/lib.rs b/libtree/src/lib.rs new file mode 100644 index 0000000..8be2797 --- /dev/null +++ b/libtree/src/lib.rs @@ -0,0 +1,257 @@ +use std::fmt::Display; + +#[derive(Clone, Default)] +pub struct Stats { + pub comparisons: u64, + pub reads: u64, + pub writes: u64, +} + +impl Stats { + pub fn new() -> Stats { + Self { + comparisons: 0, + reads: 0, + writes: 0, + } + } +} + +impl std::ops::AddAssign for Stats { + fn add_assign(&mut self, rhs: Self) { + self.comparisons += rhs.comparisons; + self.reads += rhs.reads; + self.writes += rhs.writes; + } +} + +#[derive(Clone)] +pub struct Node { + pub key: u64, + pub left: Tree, + pub right: Tree, +} + +impl Node { + pub fn new(key: u64) -> Self { + Self { + key, + left: Tree(None), + right: Tree(None), + } + } + + pub fn height(&self) -> usize { + match (&self.left.0, &self.right.0) { + (None, None) => + 1, + (None, Some(right)) => + 1 + right.height(), + (Some(left), None) => + 1 + left.height(), + (Some(left), Some(right)) => + 1 + (left.height()).max(right.height()), + } + } + + // https://stackoverflow.com/a/66661638 + fn replace_nth_char_ascii(s: &mut str, idx: usize, newchar: char) { + let s_bytes: &mut [u8] = unsafe { s.as_bytes_mut() }; + assert!(idx < s_bytes.len()); + assert!(s_bytes[idx].is_ascii()); + assert!(newchar.is_ascii()); + // we've made sure this is safe. + s_bytes[idx] = newchar as u8; + } + + fn _print(&self, f: &mut std::fmt::Formatter<'_>, depth: usize, prefix: char, left_trace: &mut String, right_trace: &mut String) -> std::fmt::Result { + if let Some(left) = &self.left.0 { + left._print(f, depth + 1, '/', left_trace, right_trace)?; + } + if prefix == '/' { + Self::replace_nth_char_ascii(left_trace, depth - 1, '|'); + } + if prefix == '\\' { + Self::replace_nth_char_ascii(right_trace, depth - 1, ' '); + } + if depth == 0 { + write!(f, "-")?; + } + if depth > 0 { + write!(f, " ")?; + } + + + if depth > 0 { + for i in 0..(depth - 1) { + if left_trace.chars().nth(i).unwrap() == '|' || right_trace.chars().nth(i).unwrap() == '|' { + write!(f, "| ")?; + } else { + write!(f, " ")?; + } + } + write!(f, "{}-", prefix)?; + } + writeln!(f, "[{}]", self.key)?; + Self::replace_nth_char_ascii(left_trace, depth, ' '); + + if let Some(right) = &self.right.0 { + Self::replace_nth_char_ascii(right_trace, depth, '|'); + right._print(f, depth + 1, '\\', left_trace, right_trace)?; + } + + Ok(()) + } +} + +#[derive(Clone)] +pub struct Tree(Option>); + +impl Tree { + pub fn insert(&mut self, key: u64) -> (bool, Stats) { + let mut stats = Stats::new(); + + if self.0.is_none() { + stats.writes += 1; + self.0 = Some(Box::new(Node::new(key))); + return (true, stats); + } + + let mut current = self; + while let Some(ref mut node) = current.0 { + stats.reads += 1; + use std::cmp::Ordering::*; + stats.comparisons += 1; + match node.key.cmp(&key) { + Greater => { + stats.reads += 1; + current = &mut node.left; + }, + Less => { + stats.reads += 1; + current = &mut node.right; + }, + Equal => return (false, stats), + } + } + + stats.writes += 1; + current.0 = Some(Box::new(Node::new(key))); + + (true, stats) + } + + pub fn delete(&mut self, key: u64) -> (bool, Stats) { + let mut current: *mut Tree = self; + + let mut stats = Stats::default(); + + unsafe { + while let Some(ref mut node) = (*current).0 { + stats.reads += 1; + use std::cmp::Ordering::*; + stats.comparisons += 1; + match node.key.cmp(&key) { + Greater => { + stats.reads += 1; + current = &mut node.left; + }, + Less => { + stats.reads += 1; + current = &mut node.right; + }, + Equal => { + match (node.left.0.as_mut(), node.right.0.as_mut()) { + (None, None) => { + stats.writes += 1; + (*current).0 = None; + }, + (None, Some(_)) => { + stats.writes += 1; + (*current).0 = node.right.0.take(); + }, + (Some(_), None) => { + stats.writes += 1; + (*current).0 = node.left.0.take(); + }, + (Some(_), Some(_)) => { + (*current).0.as_mut().unwrap().key = { + let mut to_return = None; + + stats.reads += 1; + if node.right.0.is_some() { + stats.reads += 1; + let mut current = &mut node.right; + + stats.reads += 1; + while current.0.as_ref().unwrap().left.0.is_some() { + stats.writes += 1; + current = &mut current.0.as_mut().unwrap().left; + } + + let node = current.0.take().unwrap(); + to_return = Some(node.key); + stats.writes += 1; + current.0 = node.right.0; + } + + to_return.unwrap() + }; + }, + } + return (true, stats); + }, + } + } + } + + (false, stats) + } +} + +pub struct BinarySearchTree { + pub root: Tree, + pub size: usize, + print_capacity: usize, + pub stats: Stats, +} + +impl BinarySearchTree { + pub fn new(print_capacity: usize) -> Self { + Self { + root: Tree(None), + size: 0, + print_capacity, + stats: Stats::default(), + } + } + + pub fn height(&self) -> usize { + match &self.root.0 { + Some(root) => root.height(), + None => 0, + } + } + + pub fn insert(&mut self, key: u64) -> bool { + let (ret, stats) = self.root.insert(key); + self.stats += stats; + ret + } + + pub fn delete(&mut self, key: u64) -> bool { + let (ret, stats) = self.root.delete(key); + self.stats += stats; + ret + } +} + +impl Display for BinarySearchTree { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self.root.0 { + Some(root) => root._print(f, 0, '-', &mut " ".repeat(self.print_capacity), &mut " ".repeat(self.print_capacity)), + None => Ok(()), + } + + } +}