diff --git a/crates/cs/blocks-cs-sort/src/algorithms.rs b/crates/cs/blocks-cs-sort/src/algorithms.rs index a16e6fd..52ccb96 100644 --- a/crates/cs/blocks-cs-sort/src/algorithms.rs +++ b/crates/cs/blocks-cs-sort/src/algorithms.rs @@ -53,6 +53,8 @@ assert_eq!(numbers, vec![1, 1, 3, 4, 5, 9]); // Using BubbleSort // Using InsertionSort + +// Using SelectionSort ``` */ @@ -61,6 +63,7 @@ pub mod heapsort; pub mod mergesort; pub mod bubblesort; pub mod insertionsort; +pub mod selectionsort; /// Re-export of [`quicksort::sort`]. /// @@ -83,4 +86,6 @@ pub use self::mergesort::sort as mergesort; // TODO pub use self::bubblesort::sort as bubblesort; -pub use self::insertionsort::sort as insertionsort; \ No newline at end of file +pub use self::insertionsort::sort as insertionsort; + +pub use self::selectionsort::sort as selectionsort; diff --git a/crates/cs/blocks-cs-sort/src/algorithms/selectionsort.rs b/crates/cs/blocks-cs-sort/src/algorithms/selectionsort.rs new file mode 100644 index 0000000..8342fd8 --- /dev/null +++ b/crates/cs/blocks-cs-sort/src/algorithms/selectionsort.rs @@ -0,0 +1,205 @@ +use std::fmt::Debug; + +/// Selection Sort implementation for sorting slices. +/// +/// # Algorithm Overview +/// Selection sort works by: +/// 1. Dividing the input into a sorted and unsorted region +/// 2. Finding the minimum element in the unsorted region +/// 3. Swapping it with the first element of the unsorted region +/// 4. Moving the boundary between sorted and unsorted regions one element to the right +/// +/// # Time Complexity +/// - Best Case: O(n²) +/// - Average Case: O(n²) +/// - Worst Case: O(n²) +/// +/// # Space Complexity +/// - O(1) auxiliary space +/// +/// # Stability +/// - Not stable by default (equal elements may change relative order) +/// +/// # Advantages +/// - Simple implementation +/// - Performs well on small arrays +/// - Minimizes the number of swaps (O(n) swaps vs O(n²) comparisons) +/// - In-place algorithm +/// - Works well when memory writes are expensive +pub fn sort(slice: &mut [T]) +where + T: Ord + Clone + Debug, +{ + let len = slice.len(); + + // One by one move boundary of unsorted subarray + for i in 0..len { + // Find the minimum element in unsorted array + let mut min_idx = i; + for j in (i + 1)..len { + if slice[j] < slice[min_idx] { + min_idx = j; + } + } + + // Swap the found minimum element with the first element + // Only if we found a smaller element + if min_idx != i { + slice.swap(i, min_idx); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_empty_slice() { + let mut arr: Vec = vec![]; + sort(&mut arr); + assert_eq!(arr, vec![]); + } + + #[test] + fn test_single_element() { + let mut arr = vec![1]; + sort(&mut arr); + assert_eq!(arr, vec![1]); + } + + #[test] + fn test_sorted_array() { + let mut arr = vec![1, 2, 3, 4, 5]; + sort(&mut arr); + assert_eq!(arr, vec![1, 2, 3, 4, 5]); + } + + #[test] + fn test_reverse_sorted() { + let mut arr = vec![5, 4, 3, 2, 1]; + sort(&mut arr); + assert_eq!(arr, vec![1, 2, 3, 4, 5]); + } + + #[test] + fn test_random_order() { + let mut arr = vec![3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]; + let mut expected = arr.clone(); + expected.sort(); + sort(&mut arr); + assert_eq!(arr, expected); + } + + #[test] + fn test_duplicate_elements() { + let mut arr = vec![3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]; + let mut expected = arr.clone(); + expected.sort(); + sort(&mut arr); + assert_eq!(arr, expected); + } + + #[test] + fn test_large_array() { + let mut arr: Vec = (0..1000).rev().collect(); + let mut expected = arr.clone(); + expected.sort(); + sort(&mut arr); + assert_eq!(arr, expected); + } + + #[test] + fn test_different_types() { + // Test with floating point numbers + let mut float_arr = vec![3.14, 1.41, 2.71, 0.58]; + let mut expected = float_arr.clone(); + expected.sort_by(|a, b| a.partial_cmp(b).unwrap()); + sort(&mut float_arr); + assert_eq!(float_arr, expected); + + // Test with strings + let mut string_arr = vec!["banana", "apple", "cherry", "date"]; + let mut expected = string_arr.clone(); + expected.sort(); + sort(&mut string_arr); + assert_eq!(string_arr, expected); + } + + #[test] + fn test_all_equal() { + // Test with an array where all elements are equal + let mut arr = vec![1, 1, 1, 1, 1]; + sort(&mut arr); + assert_eq!(arr, vec![1, 1, 1, 1, 1]); + } + + #[test] + fn test_alternating() { + // Test with an array that alternates between two values + let mut arr = vec![1, 2, 1, 2, 1, 2]; + sort(&mut arr); + assert_eq!(arr, vec![1, 1, 1, 2, 2, 2]); + } + + #[test] + fn test_negative_numbers() { + // Test with negative numbers + let mut arr = vec![-5, -2, -8, -1, -9]; + sort(&mut arr); + assert_eq!(arr, vec![-9, -8, -5, -2, -1]); + } + + #[test] + fn test_mixed_numbers() { + // Test with a mix of positive and negative numbers + let mut arr = vec![-3, 4, 0, -1, 5, -2]; + sort(&mut arr); + assert_eq!(arr, vec![-3, -2, -1, 0, 4, 5]); + } + + #[test] + fn test_minimal_swaps() { + // Test that we're making O(n) swaps + let mut count = 0; + let mut arr = vec![5, 4, 3, 2, 1]; + + // Wrap the slice in a type that counts swaps + struct SwapCounter<'a, T> { + slice: &'a mut [T], + swap_count: &'a mut usize, + } + + impl<'a, T> SwapCounter<'a, T> { + fn swap(&mut self, i: usize, j: usize) { + self.slice.swap(i, j); + *self.swap_count += 1; + } + } + + { + let mut counter = SwapCounter { + slice: &mut arr, + swap_count: &mut count, + }; + + // Custom selection sort that uses the counter + for i in 0..counter.slice.len() { + let mut min_idx = i; + for j in (i + 1)..counter.slice.len() { + if counter.slice[j] < counter.slice[min_idx] { + min_idx = j; + } + } + if min_idx != i { + counter.swap(i, min_idx); + } + } + } + + // For n=5, we expect at most n-1=4 swaps in selection sort + assert!(count <= arr.len() - 1, + "Expected at most {} swaps, but got {}", arr.len() - 1, count); + assert_eq!(arr, vec![1, 2, 3, 4, 5]); + } +} \ No newline at end of file