I'm starting to learn Rust. After reading through chapter 13 of the Rust Book, I've gone and implemented a handful of sorting algorithms for practice with the language.
Would love to learn some more experiences Rustacean's tips on ways to make this code more idiomatic. I have some particular difficulties with mergesort
-- I am not sure how to making some unnecessary copies without changing the function signature from Vec<u32>
to &[u32]
.
use std::collections::HashMap;
pub fn bubble_sort(vec: &mut Vec<u32>) -> &Vec<u32> {
if vec.len() == 0 {
return vec;
}
let mut swap_seen = true;
while swap_seen {
swap_seen = false;
for mut i in 0..(vec.len() - 1) {
while (i < (vec.len() - 1)) && (vec[i] > vec[i + 1]) {
let (a, b) = (vec[i], vec[i + 1]);
vec[i + 1] = a;
vec[i] = b;
swap_seen = true;
i += 1;
}
}
}
vec
}
pub fn selection_sort(vec: &mut Vec<u32>) -> &Vec<u32> {
if vec.len() == 0 {
return vec;
}
for i in 0..(vec.len()) {
let mut smallest_idx = i;
for j in (i + 1)..(vec.len()) {
if vec[j] < vec[smallest_idx] {
smallest_idx = j;
}
}
let (a, b) = (vec[i], vec[smallest_idx]);
vec[i] = b;
vec[smallest_idx] = a;
}
vec
}
pub fn counting_sort(vec: &mut Vec<u32>) -> Vec<u32> {
if vec.len() == 0 {
return vec![];
}
// The type matters. HashMap implements its methods using traits, and if you don't pick
// the right types the traits won't apply and the methods won't show up.
let mut counts: HashMap<&u32, u32> = HashMap::new();
for val in vec.iter() {
// Interesting usage note. This doesn't work:
//
// match counts.get(val) {
// Some(n_val) => counts.insert(val, n_val + 1),
// None => counts.insert(val, 1),
// };
//
// Why not?
// counts.get(val) is an immutable borrow of the counts hashmap.
// counts.insert(val) is a mutable borrow of the counts hashmap.
// This violates the constraint that only one mutable or any number of immutable
// borrows may be live at a time. However, it only throws a warning, not an error, for
// some reason. Ref:
// https://discord.com/channels/442252698964721669/448238009733742612/763950019681583152
//
// That brings us to this working code. The "entry API" is specifically designed to avoid
// this problem. In general, many APIs in Rust are designed around such concerns.
*counts.entry(val).or_default() += 1;
}
let mut sorted: Vec<u32> = vec![];
for digit in 0..=9u32 {
let digit_ref = &digit;
let digit_count = counts.get(digit_ref);
match digit_count {
Some(count) => {
for _ in 0..(*count as i32) {
sorted.push(digit);
}
},
None => (),
}
}
sorted
}
pub fn insertion_sort(vec: &mut Vec<u32>) -> &Vec<u32> {
// usize is Rust's "architecture-dependent integer size". It is u32 on 32-bit systems and
// u64 on 64-bit systems. usize is used in certain places in Rust lang where this low-level
// detail matters, e.g. if indexing into memory. It's used to represent array sizes I guess
// because array length maximum is the architecture's word size.
for i in 0..(vec.len()) {
for j in 0..i {
if vec[j] > vec[i] {
let (a, b) = (vec[i], vec[j]);
vec[j] = a;
vec[i] = b;
}
}
}
vec
}
pub fn quicksort(vec: Vec<u32>) -> Vec<u32> {
if vec.len() <= 1 {
return vec;
}
let pivot_idx = ((vec.len() as f32) / 2.0).floor() as usize;
let pivot_val = vec[pivot_idx];
let mut left: Vec<u32> = Vec::new();
let mut right: Vec<u32> = Vec::new();
for val in ([&vec[..pivot_idx], &vec[(pivot_idx + 1)..]].concat()).into_iter() {
if val < pivot_val {
left.push(val);
}
else {
right.push(val);
}
}
let mut result = quicksort(left);
result.push(pivot_val);
let mut right = quicksort(right);
result.append(&mut right);
result
}
pub fn mergesort(vec: Vec<u32>) -> Vec<u32> {
let join = |left: Vec<u32>, right: Vec<u32>| -> Vec<u32> {
let (mut j, mut k) = (0, 0);
let mut result: Vec<u32> = vec![];
while j < left.len() && k < right.len() {
if left[j] < right[k] {
result.push(left[j]);
j += 1;
}
else {
result.push(right[k]);
k += 1;
}
}
while j < left.len() {
result.push(left[j]);
j += 1;
}
while k < right.len() {
result.push(right[k]);
k += 1;
}
result
};
if vec.len() <= 1 {
return vec;
}
// Nit: eliminate this additional base case.
if vec.len() == 2 {
if vec[0] < vec[1] {
return vec;
} else {
return vec![vec[1], vec[0]];
}
}
// TODO: how do I eliminate this copy without changing the function signature from Vec<u32>
// to &[u32]?
let pivot = (((vec.len() as f32) / 2.0).floor()) as usize;
let mut left: Vec<u32> = Vec::new();
left.extend_from_slice(&vec[..pivot]);
let mut right: Vec<u32> = Vec::new();
right.extend_from_slice(&vec[pivot..]);
let left = mergesort(left);
let right = mergesort(right);
join(left, right)
}
```