diff --git a/.github/codecov.yml b/.github/codecov.yml index ad15589..8693315 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -11,9 +11,12 @@ coverage: # Avoid false negatives threshold: 1% -# Test files aren't important for coverage +# These files aren't important for coverage ignore: - "tests" + - "**/main.rs" + - "**/lib.rs" + - "**/benchmark.rs" # Make comments less noisy comment: diff --git a/zung_mini/Cargo.toml b/zung_mini/Cargo.toml index e892d16..92e0ce2 100644 --- a/zung_mini/Cargo.toml +++ b/zung_mini/Cargo.toml @@ -11,3 +11,7 @@ keywords = ["projects", "learning", "mini"] [dependencies] clap = { version = "4.5.18", features = ["derive"] } +indicatif = "0.17" +colored = "2.1.0" +rand = "0.8" +prettytable = "0.10.0" diff --git a/zung_mini/README.md b/zung_mini/README.md index 2fab891..4b25a2b 100644 --- a/zung_mini/README.md +++ b/zung_mini/README.md @@ -10,6 +10,10 @@ _This library is intended for **learning purposes only**. While I will do my bes - [Mini Project 1](#mini-project-1---progbar) - [Features](#features) +- [Mini Project 2](#mini-project-2---strsplit) + - [Features](#features) +- [Mini Project 3](#mini-project-3---orst) + - [Features](#features) - [Usage](#usage) # Mini Project 1 - ProgBar @@ -31,11 +35,16 @@ The [`ProgBar`](https://docs.rs/zung_mini/latest/zung_mini/progbar/index.html) m The [`Strsplit`](https://docs.rs/zung_mini/latest/zung_mini/strsplit/index.html) module provides an efficient, iterator-based string splitting utility for Rust. It extends both `String` and `&str` types with a `strsplit` method, allowing users to split strings based on a specified delimiter and iterate over the resulting substrings lazily or collect them all at once. This is particularly useful when you need efficient and flexible string splitting behavior. +# Mini Project 3 - Orst + +**\_ _Implementation of custom sorting algorithms along with a benchmark following [Crust of Rust: Sorting Algorithms](https://www.youtube.com/watch?v=h4RkCyJyXmM)_** + +The [`Orst`](https://docs.rs/zung_mini/latest/zung_mini/orst/index.html) module provides an custom implementations of sorting algorithms along with a simple algorithm. + ## Features -- **Lazy Evaluation**: The splitting is performed lazily as the iterator progresses, avoiding unnecessary allocation or processing. -- **Supports Bounded Splits**: You can split on any valid substring and return the result immediately upon the first match. -- **Panics on Empty Needle**: Ensures correct usage by panicking if an empty delimiter is passed. +- It sorts stuff. +- Easy to use. --- diff --git a/zung_mini/src/lib.rs b/zung_mini/src/lib.rs index 9a93828..176cb05 100644 --- a/zung_mini/src/lib.rs +++ b/zung_mini/src/lib.rs @@ -2,6 +2,7 @@ //! //! Mini rust projects that target specific features of rust +pub mod orst; pub mod progbar; pub mod strsplit; @@ -32,6 +33,9 @@ enum MiniCommands { #[command(subcommand)] command: StrsplitCommands, }, + + /// Run custom sorting algorithms. + Orst, } #[derive(Clone, Subcommand, Debug)] @@ -128,10 +132,12 @@ impl MiniArgs { println!("{:?}", result); } StrsplitCommands::Until { needle, string } => { - let result = string.till_needle(needle); + let result = string.strsplit(needle).till_needle(); println!("{:?}", result); } }, + + MiniCommands::Orst => orst::benchmark::run_orst(), } } } diff --git a/zung_mini/src/orst/benchmark.rs b/zung_mini/src/orst/benchmark.rs new file mode 100644 index 0000000..d368a1c --- /dev/null +++ b/zung_mini/src/orst/benchmark.rs @@ -0,0 +1,234 @@ +use colored::Colorize; +use rand::{self, Rng}; +use std::{cell::Cell, rc::Rc, time::Instant}; + +use prettytable::{row, Table}; + +use super::{BubbleSorter, InsertionSorter, QuickSorter, SelectionSorter, Sorter}; + +const ZERO: usize = 0; +const ONE: usize = 1; +const HUNDRED: usize = 100; +const TEN_THOUSAND: usize = 10_000; +const HUNDRED_THOUSAND: usize = 100_000; +const MILLION: usize = 1_000_000; +const HUNDRED_MILLION: usize = 100_000_000; + +// In this the `elem` will be compared and the `comparison_counter` will be ignored. +#[derive(Clone)] +struct SortEvaluator { + // For making the comparisons + elem: T, + // This counter will update every time the `elem` is compared. + // Therefore, obviously this has to be a mutable value. + // Therefore, it is rapped in reference counter and a cell + comparison_counter: Rc>, +} + +impl SortEvaluator { + fn new(elem: T, comparison_counter: Rc>) -> Self { + Self { + elem, + comparison_counter, + } + } +} + +// Trait for equality comparisons which are equivalence relations. +// +// This means, that in addition to a == b and a != b being strict inverses, the equality +// must be (for all a, b and c): +// +// reflexive: a == a; +// symmetric: a == b implies b == a; and +// transitive: a == b and b == c implies a == c. +// +// This property cannot be checked by the compiler, and therefore Eq implies PartialEq, +// and has no extra methods. +impl Eq for SortEvaluator {} + +// This trait allows for partial equality, +// for types that do not have a full equivalence relation. +// For example, in floating point numbers NaN != NaN, +// so floating point types implement PartialEq but not Eq. +// Formally speaking, when Rhs == Self, +// this trait corresponds to a partial equivalence relation. +impl PartialEq for SortEvaluator { + fn eq(&self, other: &Self) -> bool { + self.comparison_counter + .set(self.comparison_counter.get() + 1); + self.elem == other.elem + } +} + +// Trait for types that form a partial order. +// +// The lt, le, gt, and ge methods of this trait can be called using the <, <=, >, and >= operators, respectively. +// +// The methods of this trait must be consistent with each other and with those of PartialEq. The following conditions must hold: +// +// 1. a == b if and only if partial_cmp(a, b) == Some(Equal). +// 2. a < b if and only if partial_cmp(a, b) == Some(Less) +// 3. a > b if and only if partial_cmp(a, b) == Some(Greater) +// 4. a <= b if and only if a < b || a == b +// 5. a >= b if and only if a > b || a == b +// 6. a != b if and only if !(a == b). +// +impl PartialOrd for SortEvaluator { + fn partial_cmp(&self, other: &Self) -> Option { + self.comparison_counter + .set(self.comparison_counter.get() + 1); + self.elem.partial_cmp(&other.elem) + } +} + +// Trait for types that form a total order. +// +// Implementations must be consistent with the PartialOrd implementation, +// and ensure max, min, and clamp are consistent with cmp: +// +// partial_cmp(a, b) == Some(cmp(a, b)). +// max(a, b) == max_by(a, b, cmp) (ensured by the default implementation). +// min(a, b) == min_by(a, b, cmp) (ensured by the default implementation). +// For a.clamp(min, max), see the method docs (ensured by the default implementation). +// +// It’s easy to accidentally make cmp and partial_cmp disagree by deriving +// some of the traits and manually implementing others. +impl Ord for SortEvaluator { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.comparison_counter + .set(self.comparison_counter.get() + 1); + self.elem.cmp(&other.elem) + } +} + +fn run_bench( + sorter: S, + values: &mut [SortEvaluator], + comparisons: Rc>, +) -> usize +where + T: Ord + Eq + Clone, + S: Sorter>, +{ + comparisons.set(0); + sorter.sort(values); + + comparisons.get() +} + +pub fn run_orst() { + let mut random = rand::thread_rng(); + let counter = Rc::new(Cell::new(0)); + for &n in &[ + ZERO, + ONE, + HUNDRED, + TEN_THOUSAND, + HUNDRED_THOUSAND, + MILLION, + HUNDRED_MILLION, + ] { + let mut values = Vec::with_capacity(n); + for _ in 0..n { + values.push(SortEvaluator::new(random.gen::(), counter.clone())); + } + + println!( + "{} {}", + "List Size -> ".bold().underline().blue(), + n.to_string().bold() + ); + + let mut table = Table::new(); + table.add_row(row![ + "Sorter".bold(), + "Comparisons Made".bold(), + "Time Taken".bold() + ]); + + if n <= HUNDRED_THOUSAND { + let now = Instant::now(); + let took = run_bench(BubbleSorter, &mut values, counter.clone()); + table.add_row(row![ + "Bubble Sort", + took.to_string(), + format!("{:?}", now.elapsed()) + ]); + } else { + table.add_row(row!["Bubble Sort", "Not Doing It".red(), "It is Stupid"]); + } + + if n <= HUNDRED_THOUSAND { + let now = Instant::now(); + let took = run_bench( + InsertionSorter { smart: true }, + &mut values, + counter.clone(), + ); + + table.add_row(row![ + "Insertion Sort", + took.to_string(), + format!("{:?}", now.elapsed()) + ]); + + let now = Instant::now(); + let took = run_bench( + InsertionSorter { smart: false }, + &mut values, + counter.clone(), + ); + + table.add_row(row![ + "Insertion Sort (not smart)", + took.to_string(), + format!("{:?}", now.elapsed()) + ]); + } else { + table.add_row(row!["Insertion Sort", "Not Doing It".red(), "It is Stupid"]); + } + + if n <= HUNDRED_THOUSAND { + let now = Instant::now(); + let took = run_bench(SelectionSorter, &mut values, counter.clone()); + table.add_row(row![ + "Selection Sort", + took.to_string(), + format!("{:?}", now.elapsed()) + ]); + } else { + table.add_row(row!["Selection Sort", "Not Doing It".red(), "It is Stupid"]); + } + + let now = Instant::now(); + let took = run_bench(QuickSorter, &mut values, counter.clone()); + + table.add_row(row![ + "Quick Sort", + took.to_string(), + format!("{:?}", now.elapsed()) + ]); + + // TODO: Implement this. + // + // let now = Instant::now(); + // let took = run_bench(StdSorter { stable: true }, &mut values, counter.clone()); + // table.add_row(row![ + // "Standard Library Sort Stable", + // took.to_string(), + // format!("{:?}", now.elapsed()) + // ]); + // + // let now = Instant::now(); + // let took = run_bench(StdSorter { stable: false }, &mut values, counter.clone()); + // table.add_row(row![ + // "Standart Library Sort Unstable", + // took.to_string(), + // format!("{:?}", now.elapsed()) + // ]); + + table.printstd(); + println!(); + } +} diff --git a/zung_mini/src/orst/mod.rs b/zung_mini/src/orst/mod.rs new file mode 100644 index 0000000..fe8ae3c --- /dev/null +++ b/zung_mini/src/orst/mod.rs @@ -0,0 +1,37 @@ +//! Implementation of sorting algorithms from [Crust of Rust: Sorting +//! Algorithms](https://www.youtube.com/watch?v=h4RkCyJyXmM) +//! +//! # Example +//! +//! ``` +//! use zung_mini::orst::BubbleSorter; +//! use zung_mini::orst::Sorter; +//! +//! let mut slice = vec![1, 3, 2, 5, 4]; +//! BubbleSorter.sort(&mut slice); +//! assert_eq!(vec![1, 2, 3, 4, 5], slice); +//! ``` + +pub mod benchmark; +mod sorters; + +pub use sorters::bubble_sorter::BubbleSorter; +pub use sorters::insertion_sorter::InsertionSorter; +pub use sorters::quick_sorter::QuickSorter; +pub use sorters::selection_sorter::SelectionSorter; + +/// The sorting algorithm must implement the trait `Sorter`. +pub trait Sorter +where + T: Ord, +{ + fn sort(&self, slice: &mut [T]); +} + +pub trait Sort +where + S: Sorter, + T: Ord, +{ + fn orst(&mut self); +} diff --git a/zung_mini/src/orst/sorters/bubble_sorter.rs b/zung_mini/src/orst/sorters/bubble_sorter.rs new file mode 100644 index 0000000..9e4858e --- /dev/null +++ b/zung_mini/src/orst/sorters/bubble_sorter.rs @@ -0,0 +1,120 @@ +use crate::orst::Sorter; +use indicatif::{ProgressBar, ProgressStyle}; + +/// An implementation of [Bubble Sort](https://en.wikipedia.org/wiki/Bubble_sort) +/// +/// # Usage +///``` +/// use zung_mini::orst::{BubbleSorter, Sorter}; +/// +/// let mut slice = [1, 5, 4, 2, 3]; +/// BubbleSorter.sort(&mut slice); +/// assert_eq!(slice, [1, 2, 3, 4, 5]); +///``` +/// # Explanation +/// +/// Bubble sort, sometimes referred to as sinking sort, +/// is a simple sorting algorithm that repeatedly steps +/// through the list, compares adjacent elements and swaps +/// them if they are in the wrong order. The pass through +/// the list is repeated until the list is sorted. The +/// algorithm, which is a comparison sort, is named for the +/// way smaller or larger elements "bubble" to the top of the list. +/// +/// +/// # Algorithm +/// +/// ``` +/// let mut slice = vec![1, 3, 2, 5, 4]; +/// +/// let mut swapped = true; +/// +/// while swapped { +/// swapped = false; +/// for i in 0..(slice.len() - 1) { +/// // swap the elements at index if the current element is +/// // bigger that the next element. +/// if slice[i] > slice[i + 1] { +/// slice.swap(i, i + 1); +/// swapped = true; +/// } +/// } +/// } +/// ``` +#[derive(Default)] +pub struct BubbleSorter; + +impl Sorter for BubbleSorter +where + T: Ord, +{ + #[inline] + fn sort(&self, slice: &mut [T]) { + let pb = ProgressBar::new(slice.len() as u64); + pb.set_style( + ProgressStyle::with_template( + "Bubble Sort -> {spinner:.green} [{elapsed_precise}] [{bar:50.cyan/blue}] On Slice: ({pos}/{len}, ETA: {eta})", + ) + .unwrap(), + ); + + let mut swapped = true; + + while swapped { + swapped = false; + for i in 1..slice.len() { + if slice[i - 1] > slice[i] { + slice.swap(i - 1, i); + swapped = true; + } + } + pb.inc(1); + } + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn arbitrary_array() { + let mut slice = [1, 5, 4, 2, 3]; + BubbleSorter.sort(&mut slice); + assert_eq!(slice, [1, 2, 3, 4, 5]); + } + + #[test] + fn sorted_array() { + let mut slice = (1..10).collect::>(); + BubbleSorter.sort(&mut slice); + assert_eq!(slice, (1..10).collect::>()); + } + + #[test] + fn very_unsorted() { + let mut slice = (1..1000).rev().collect::>(); + BubbleSorter.sort(&mut slice); + assert_eq!(slice, (1..1000).collect::>()); + } + + #[test] + fn simple_edge_cases() { + let mut one = vec![1]; + BubbleSorter.sort(&mut one); + assert_eq!(one, vec![1]); + + let mut two = vec![1, 2]; + BubbleSorter.sort(&mut two); + assert_eq!(two, vec![1, 2]); + + let mut two = vec![2, 1]; + BubbleSorter.sort(&mut two); + assert_eq!(two, vec![1, 2]); + + let mut three = vec![3, 1, 2]; + BubbleSorter.sort(&mut three); + assert_eq!(three, vec![1, 2, 3]); + } +} diff --git a/zung_mini/src/orst/sorters/insertion_sorter.rs b/zung_mini/src/orst/sorters/insertion_sorter.rs new file mode 100644 index 0000000..ab82e45 --- /dev/null +++ b/zung_mini/src/orst/sorters/insertion_sorter.rs @@ -0,0 +1,153 @@ +use indicatif::{ProgressBar, ProgressStyle}; + +use crate::orst::Sorter; + +/// An implementation of [Insertion Sort](https://en.wikipedia.org/wiki/Insertion_sort) +/// +/// # Explanation +/// +/// Insertion sort is a simple sorting algorithm that builds the final sorted array (or list) one +/// item at a time +/// +/// Insertion sort iterates, consuming one input element each repetition, and grows a sorted output +/// list. At each iteration, insertion sort removes one element from the input data, finds the +/// location it belongs within the sorted list, and inserts it there. It repeats until no input +/// elements remain. +/// +/// Sorting is typically done in-place, by iterating up the array, growing the sorted list behind +/// it. At each array-position, it checks the value there against the largest value in the sorted +/// list (which happens to be next to it, in the previous array- position checked). If larger, it +/// leaves the element in place and moves to the next. If smaller, it finds the correct position +/// within the sorted list, shifts all the larger values up to make a space, and inserts into that +/// correct position. +/// +/// # Usage +///``` +/// use zung_mini::orst::{InsertionSorter, Sorter}; +/// +/// let mut slice = [1, 5, 4, 2, 3]; +/// InsertionSorter{ smart: true }.sort(&mut slice); +/// assert_eq!(slice, [1, 2, 3, 4, 5]); +///``` +pub struct InsertionSorter { + pub smart: bool, +} + +impl Sorter for InsertionSorter +where + T: Ord, +{ + #[inline] + fn sort(&self, slice: &mut [T]) { + let pb = ProgressBar::new(slice.len() as u64); + pb.set_style( + ProgressStyle::with_template( + "Insertion Sort -> {spinner:.green} [{elapsed_precise}] [{bar:50.cyan/blue}] On Slice: ({pos}/{len}, ETA: {eta})", + ) + .unwrap(), + ); + + for unsorted in 1..slice.len() { + if !self.smart { + let mut i = unsorted; + while i > 0 && slice[i - 1] > slice[i] { + slice.swap(i - 1, i); + i -= 1; + } + } else { + let i = match slice[..unsorted].binary_search(&slice[unsorted]) { + Ok(i) => i, + Err(i) => i, + }; + slice[i..=unsorted].rotate_right(1); + } + pb.inc(1); + } + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn arbitrary_array_smart() { + let mut slice = [1, 5, 4, 2, 3]; + InsertionSorter { smart: true }.sort(&mut slice); + assert_eq!(slice, [1, 2, 3, 4, 5]); + } + + #[test] + fn arbitrary_array_lame() { + let mut slice = [1, 5, 4, 2, 3]; + InsertionSorter { smart: false }.sort(&mut slice); + assert_eq!(slice, [1, 2, 3, 4, 5]); + } + + #[test] + fn sorted_array_smart() { + let mut slice = (1..10).collect::>(); + InsertionSorter { smart: true }.sort(&mut slice); + assert_eq!(slice, (1..10).collect::>()); + } + + #[test] + fn sorted_array_lame() { + let mut slice = (1..10).collect::>(); + InsertionSorter { smart: false }.sort(&mut slice); + assert_eq!(slice, (1..10).collect::>()); + } + + #[test] + fn very_unsorted_smart() { + let mut slice = (1..1000).rev().collect::>(); + InsertionSorter { smart: true }.sort(&mut slice); + assert_eq!(slice, (1..1000).collect::>()); + } + + #[test] + fn very_unsorted_lame() { + let mut slice = (1..1000).rev().collect::>(); + InsertionSorter { smart: false }.sort(&mut slice); + assert_eq!(slice, (1..1000).collect::>()); + } + + #[test] + fn simple_edge_cases_smart() { + let mut one = vec![1]; + InsertionSorter { smart: true }.sort(&mut one); + assert_eq!(one, vec![1]); + + let mut two = vec![1, 2]; + InsertionSorter { smart: true }.sort(&mut two); + assert_eq!(two, vec![1, 2]); + + let mut two = vec![2, 1]; + InsertionSorter { smart: true }.sort(&mut two); + assert_eq!(two, vec![1, 2]); + + let mut three = vec![3, 1, 2]; + InsertionSorter { smart: true }.sort(&mut three); + assert_eq!(three, vec![1, 2, 3]); + } + + #[test] + fn simple_edge_cases_lame() { + let mut one = vec![1]; + InsertionSorter { smart: false }.sort(&mut one); + assert_eq!(one, vec![1]); + + let mut two = vec![1, 2]; + InsertionSorter { smart: false }.sort(&mut two); + assert_eq!(two, vec![1, 2]); + + let mut two = vec![2, 1]; + InsertionSorter { smart: false }.sort(&mut two); + assert_eq!(two, vec![1, 2]); + + let mut three = vec![3, 1, 2]; + InsertionSorter { smart: false }.sort(&mut three); + assert_eq!(three, vec![1, 2, 3]); + } +} diff --git a/zung_mini/src/orst/sorters/mod.rs b/zung_mini/src/orst/sorters/mod.rs new file mode 100644 index 0000000..3fe2b80 --- /dev/null +++ b/zung_mini/src/orst/sorters/mod.rs @@ -0,0 +1,7 @@ +pub(crate) mod bubble_sorter; + +pub(crate) mod insertion_sorter; + +pub(crate) mod selection_sorter; + +pub(crate) mod quick_sorter; diff --git a/zung_mini/src/orst/sorters/quick_sorter.rs b/zung_mini/src/orst/sorters/quick_sorter.rs new file mode 100644 index 0000000..89950bb --- /dev/null +++ b/zung_mini/src/orst/sorters/quick_sorter.rs @@ -0,0 +1,155 @@ +use indicatif::{ProgressBar, ProgressStyle}; +use rand::Rng; + +use crate::orst::Sorter; + +/// An implementation of [Quick Sort](https://en.wikipedia.org/wiki/Quicksort) +/// +/// # Usage +///``` +/// use zung_mini::orst::{QuickSorter, Sorter}; +/// +/// let mut slice = [1, 5, 4, 2, 3]; +/// QuickSorter.sort(&mut slice); +/// assert_eq!(slice, [1, 2, 3, 4, 5]); +///``` +/// +/// # Explanation +/// +/// Quicksort is an in-place sorting algorithm. Developed +/// by British computer scientist Tony Hoare in 1959 and published +/// in 1961 it is still a commonly used algorithm for +/// sorting. When implemented well, it can be somewhat +/// faster than merge sort and about two or three times +/// faster than heapsort. +/// +/// # Algorithm +/// +/// Quicksort is a divide-and-conquer algorithm. +/// It works by selecting a 'pivot' element from +/// the array and partitioning the other elements into two sub +/// -arrays, according to whether they are less than +/// or greater than the pivot. For this reason, +/// it is sometimes called partition-exchange sort. +/// The sub-arrays are then sorted recursively. +/// This can be done in-place, requiring small +/// additional amounts of memory to perform the sorting. +pub struct QuickSorter; + +fn quicksort(slice: &mut [T]) { + const INSERTION_THRESHOLD: usize = 10; + + let pb = ProgressBar::new(slice.len() as u64); + pb.set_style( + ProgressStyle::with_template( + "Quick Sort -> {spinner:.green} [{elapsed_precise}] {bar:50.cyan/blue} On Slice: {pos}/{len}, ETA: {eta}", + ) + .unwrap(), + ); + + // Define a closure to encapsulate the counter + let quicksort_with_pb = |slice: &mut [T]| { + fn inner_quicksort(slice: &mut [T], pb: &ProgressBar) { + if slice.len() <= INSERTION_THRESHOLD { + slice.sort(); + return; + } + + let pivot_index = rand::thread_rng().gen_range(0..slice.len()); + slice.swap(0, pivot_index); + + let (pivot, rest) = slice.split_first_mut().expect("Unexpected empty slice"); + let mut left = 0; + let mut right = rest.len() - 1; + + while left <= right { + if &rest[left] <= pivot { + left += 1; + } else if &rest[right] > pivot { + if right == 0 { + break; + } + right -= 1; + } else { + rest.swap(left, right); + left += 1; + if right == 0 { + break; + } + right -= 1; + } + } + + let left = left + 1; + slice.swap(0, left - 1); + + let (left_part, right_part) = slice.split_at_mut(left - 1); + assert!(left_part.last() <= right_part.first()); + + pb.inc(1); + inner_quicksort(left_part, pb); + inner_quicksort(&mut right_part[1..], pb); + } + + // Call the inner recursive function + inner_quicksort(slice, &pb); + }; + + quicksort_with_pb(slice); +} + +impl Sorter for QuickSorter +where + T: Ord, +{ + #[inline] + fn sort(&self, slice: &mut [T]) { + quicksort(slice) + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn arbitrary_array() { + let mut slice = [1, 5, 4, 2, 3]; + QuickSorter.sort(&mut slice); + assert_eq!(slice, [1, 2, 3, 4, 5]); + } + + #[test] + fn sorted_array() { + let mut slice = (1..10).collect::>(); + QuickSorter.sort(&mut slice); + assert_eq!(slice, (1..10).collect::>()); + } + + #[test] + fn very_unsorted() { + let mut slice = (1..1000).rev().collect::>(); + QuickSorter.sort(&mut slice); + assert_eq!(slice, (1..1000).collect::>()); + } + + #[test] + fn simple_edge_cases() { + let mut one = vec![1]; + QuickSorter.sort(&mut one); + assert_eq!(one, vec![1]); + + let mut two = vec![1, 2]; + QuickSorter.sort(&mut two); + assert_eq!(two, vec![1, 2]); + + let mut two = vec![2, 1]; + QuickSorter.sort(&mut two); + assert_eq!(two, vec![1, 2]); + + let mut three = vec![3, 1, 2]; + QuickSorter.sort(&mut three); + assert_eq!(three, vec![1, 2, 3]); + } +} diff --git a/zung_mini/src/orst/sorters/selection_sorter.rs b/zung_mini/src/orst/sorters/selection_sorter.rs new file mode 100644 index 0000000..4d81e75 --- /dev/null +++ b/zung_mini/src/orst/sorters/selection_sorter.rs @@ -0,0 +1,160 @@ +use indicatif::{ProgressBar, ProgressStyle}; + +use crate::orst::Sorter; + +/// An implementation of [Selection Sort](https://en.wikipedia.org/wiki/Selection_sort) +/// +/// # Usage +///``` +/// use zung_mini::orst::{SelectionSorter, Sorter}; +/// +/// let mut slice = [1, 5, 4, 2, 3]; +/// SelectionSorter.sort(&mut slice); +/// assert_eq!(slice, [1, 2, 3, 4, 5]); +///``` +/// # Explanation +/// +/// Selection sort is an in-place comparison sorting +/// algorithm. It has an O(n2) time complexity, which +/// makes it inefficient on large lists, and generally +/// performs worse than the similar insertion sort. Selection sort is noted for its +/// simplicity and has performance advantages over more complicated algorithms +/// in certain situations, particularly where auxiliary memory is +/// limited. +/// +/// # Algorithm +/// +/// The algorithm divides the input list into two parts: +/// a sorted sublist of items which is built +/// up from left to right at the front ( +/// left) of the list and a sublist of +/// the remaining unsorted items that occupy the rest of +/// the list. Initially, the sorted sublist is +/// empty and the unsorted sublist is the entire input +/// list. The algorithm proceeds by finding the smallest +/// (or largest, depending on sorting order) +/// element in the unsorted sublist, exchanging (swapping +/// ) it with the leftmost unsorted element (putting +/// it in sorted order), and moving the sublist +/// boundaries one element to the right. +pub struct SelectionSorter; + +impl Sorter for SelectionSorter +where + T: Ord, +{ + fn sort(&self, slice: &mut [T]) { + let pb = ProgressBar::new(slice.len() as u64); + pb.set_style( + ProgressStyle::with_template( + "Selection Sort -> {spinner:.green} [{elapsed_precise}] [{bar:50.cyan/blue}] On Slice: ({pos}/{len}, ETA: {eta})", + ) + .unwrap(), + ); + for unsorted in 0..slice.len() { + let mut smallest_in_rest = unsorted; + for i in (unsorted + 1)..slice.len() { + if slice[i] < slice[smallest_in_rest] { + smallest_in_rest = i; + } + } + if unsorted != smallest_in_rest { + slice.swap(unsorted, smallest_in_rest); + } + pb.inc(1); + } + } +} + +#[test] +fn works() { + let mut things = vec![4, 2, 3, 5, 1]; + SelectionSorter.sort(&mut things); + assert_eq!(things, &[1, 2, 3, 4, 5]) +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn arbitrary_array_smart() { + let mut slice = [1, 5, 4, 2, 3]; + SelectionSorter.sort(&mut slice); + assert_eq!(slice, [1, 2, 3, 4, 5]); + } + + #[test] + fn arbitrary_array_lame() { + let mut slice = [1, 5, 4, 2, 3]; + SelectionSorter.sort(&mut slice); + assert_eq!(slice, [1, 2, 3, 4, 5]); + } + + #[test] + fn sorted_array_smart() { + let mut slice = (1..10).collect::>(); + SelectionSorter.sort(&mut slice); + assert_eq!(slice, (1..10).collect::>()); + } + + #[test] + fn sorted_array_lame() { + let mut slice = (1..10).collect::>(); + SelectionSorter.sort(&mut slice); + assert_eq!(slice, (1..10).collect::>()); + } + + #[test] + fn very_unsorted_smart() { + let mut slice = (1..1000).rev().collect::>(); + SelectionSorter.sort(&mut slice); + assert_eq!(slice, (1..1000).collect::>()); + } + + #[test] + fn very_unsorted_lame() { + let mut slice = (1..1000).rev().collect::>(); + SelectionSorter.sort(&mut slice); + assert_eq!(slice, (1..1000).collect::>()); + } + + #[test] + fn simple_edge_cases_smart() { + let mut one = vec![1]; + SelectionSorter.sort(&mut one); + assert_eq!(one, vec![1]); + + let mut two = vec![1, 2]; + SelectionSorter.sort(&mut two); + assert_eq!(two, vec![1, 2]); + + let mut two = vec![2, 1]; + SelectionSorter.sort(&mut two); + assert_eq!(two, vec![1, 2]); + + let mut three = vec![3, 1, 2]; + SelectionSorter.sort(&mut three); + assert_eq!(three, vec![1, 2, 3]); + } + + #[test] + fn simple_edge_cases_lame() { + let mut one = vec![1]; + SelectionSorter.sort(&mut one); + assert_eq!(one, vec![1]); + + let mut two = vec![1, 2]; + SelectionSorter.sort(&mut two); + assert_eq!(two, vec![1, 2]); + + let mut two = vec![2, 1]; + SelectionSorter.sort(&mut two); + assert_eq!(two, vec![1, 2]); + + let mut three = vec![3, 1, 2]; + SelectionSorter.sort(&mut three); + assert_eq!(three, vec![1, 2, 3]); + } +} diff --git a/zung_mini/src/strsplit/mod.rs b/zung_mini/src/strsplit/mod.rs index 5a72017..37a0228 100644 --- a/zung_mini/src/strsplit/mod.rs +++ b/zung_mini/src/strsplit/mod.rs @@ -66,32 +66,6 @@ where fn strsplit

(&'a self, needle: P) -> Strsplit<'a, P> where P: 'b + AsRef; - - /// Returns the substring before the first occurrence of the given `needle` - /// without scanning the entire string. - /// - /// This function splits the string using the provided `needle` and immediately - /// returns the portion of the string before the first occurrence of the `needle`. - /// It stops searching once the `needle` is found, making it efficient as it - /// avoids iterating over the entire string unnecessarily. If the `needle` is not found, - /// the function returns the entire string. - /// - /// # Example - /// - /// ```rust - /// use zung_mini::strsplit::StrsplitExt; - /// let text = "hello world"; - /// - /// let result = text.till_needle(" "); - /// assert_eq!(result, "hello"); - /// ``` - fn till_needle

(&'a self, needle: P) -> &str - where - P: 'b + AsRef + Clone, - { - let mut splitter = self.strsplit(needle.clone()); - splitter.next().unwrap() - } } impl<'a, 'b> StrsplitExt<'a, 'b> for String @@ -123,6 +97,7 @@ where /// occurrences of the delimiter. /// /// This type is constructed by the [`strsplit()`](StrsplitExt::strsplit()) method. +#[derive(Debug, Clone, Copy)] pub struct Strsplit<'a, N> { remainder: Option<&'a str>, needle: N, @@ -160,6 +135,28 @@ where pub fn into_vec(self) -> Vec<&'a str> { self.collect() } + + /// Returns the substring before the first occurrence of the given `needle` + /// without scanning the entire string. + /// + /// This function splits the string using the provided `needle` and immediately + /// returns the portion of the string before the first occurrence of the `needle`. + /// It stops searching once the `needle` is found, making it efficient as it + /// avoids iterating over the entire string unnecessarily. If the `needle` is not found, + /// the function returns the entire string. + /// + /// # Example + /// + /// ```rust + /// use zung_mini::strsplit::StrsplitExt; + /// let text = "hello world"; + /// + /// let result = text.strsplit(" ").till_needle(); + /// assert_eq!(result, "hello"); + /// ``` + pub fn till_needle(&mut self) -> &'a str { + self.next().unwrap() + } } impl<'a, N> Iterator for Strsplit<'a, N> @@ -181,6 +178,15 @@ where } } +impl<'a, N> From> for Vec<&'a str> +where + N: 'a + AsRef, +{ + fn from(value: Strsplit<'a, N>) -> Self { + value.into_vec() + } +} + fn find_needle(needle: &str, haystack: &str) -> Option<(usize, usize)> { haystack .find(needle) @@ -245,31 +251,49 @@ mod tests { assert_eq!(a.strsplit(",").into_vec(), vec!["a b c", " d e f"]); } + #[test] + fn strsplit_from_and_into() { + let strsplit = "a b c d e f".strsplit(" "); + let vec1 = Vec::from(strsplit); + let vec2: Vec<&str> = strsplit.into(); + + assert_eq!(vec1, vec2) + } + + #[test] + fn strsplit_empty_haystack() { + let haystack = ""; + let needle = ","; + + let vec = haystack.strsplit(needle).into_vec(); + assert_eq!(vec, vec![""]); + } + #[test] fn till_needle_finds_substring() { let text = "hello world"; - let result = text.till_needle(" "); + let result = text.strsplit(" ").till_needle(); assert_eq!(result, "hello"); } #[test] fn till_needle_returns_entire_string_if_needle_not_found() { let text = "hello"; - let result = text.till_needle(","); + let result = text.strsplit(" ").till_needle(); assert_eq!(result, "hello"); } #[test] fn till_needle_with_multiple_occurrences() { let text = "apple,banana,orange"; - let result = text.till_needle(","); + let result = text.strsplit(",").till_needle(); assert_eq!(result, "apple"); // Stops at first occurrence } #[test] fn till_needle_returns_none_for_empty_string() { let text = ""; - let result = text.till_needle(","); + let result = text.strsplit(" ").till_needle(); assert_eq!(result, ""); } @@ -277,20 +301,20 @@ mod tests { #[should_panic(expected = "Empty needle is not allowed")] fn till_needle_empty_needle_panics() { let text = "example"; - let _ = text.till_needle(""); // Should panic due to empty needle + let _ = text.strsplit("").till_needle(); // Should panic due to empty needle } #[test] fn till_needle_handles_special_characters() { let text = "foo@bar.com"; - let result = text.till_needle("@"); + let result = text.strsplit("@").till_needle(); assert_eq!(result, "foo"); } #[test] fn till_needle_works_with_longer_needle() { let text = "this is a test string"; - let result = text.till_needle("is"); + let result = text.strsplit("is").till_needle(); assert_eq!(result, "th"); } }