Skip to content

Commit

Permalink
Merge pull request #32 from KeplerOps/feature/issue-21-selectionsort
Browse files Browse the repository at this point in the history
Issue #21: Implement Selection Sort
  • Loading branch information
Brad-Edwards authored Jan 14, 2025
2 parents 3006a81 + bdfd4e7 commit 6a8a74e
Show file tree
Hide file tree
Showing 2 changed files with 211 additions and 1 deletion.
7 changes: 6 additions & 1 deletion crates/cs/blocks-cs-sort/src/algorithms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ assert_eq!(numbers, vec![1, 1, 3, 4, 5, 9]);
// Using BubbleSort
// Using InsertionSort
// Using SelectionSort
```
*/

Expand All @@ -61,6 +63,7 @@ pub mod heapsort;
pub mod mergesort;
pub mod bubblesort;
pub mod insertionsort;
pub mod selectionsort;

/// Re-export of [`quicksort::sort`].
///
Expand All @@ -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;
pub use self::insertionsort::sort as insertionsort;

pub use self::selectionsort::sort as selectionsort;
205 changes: 205 additions & 0 deletions crates/cs/blocks-cs-sort/src/algorithms/selectionsort.rs
Original file line number Diff line number Diff line change
@@ -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<T>(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<i32> = 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<i32> = (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]);
}
}

0 comments on commit 6a8a74e

Please sign in to comment.