A Little Note about Algorithm in Rust


This post is an algorithm note to me but I hope this could help others too. My problem source is from LeetCode, which is nothing but simple.

CheatSheet

In this section, some useful in-built or template like solution will be listed here. Besides, I will further clarify some misunderstood concepts for people to review before attending the coding interview.

/// Sort
/// Reference -- https://doc.rust-lang.org/std/primitive.slice.html#method.sort_unstable
/// There are typcial and useful tips
let arr = vec![4, -5, 1, -3, 2];
// NOTE: DEFAULT ordering is **ASC**
arr.sort_unstable(); // [-5, -3, 1, 2, 4]
arr.sort_unstable_by(|a,b| b.cmp(a)); // reverse sorting -> [4, 2, 1, -3, -5]
// Customised case 1
let mut nums = vec![3, 30, 34, 5];
nums.sort_unstable_by(|&s, &t| {
    let one = format!("{s}{t}");
    let two = format!("{t}{s}");
    two.cmp(&one)
}); // Output: [5, 34, 3, 30]
// Customised case 2
let mut stk = vec![
    (10, 2, 3, 4),
    (10, 2, 3, 1),
    (5, 8, 9, 2),
    (10, 1, 5, 3),
    (10, 2, 1, 3),
];
stk.sort_unstable_by(|a, b| {
    b.0.cmp(&a.0)                     // 1. Sort `b.0` descending (reverse order of `a.0` and `b.0`)
        .then_with(|| b.1.cmp(&a.1))  // 2. If `b.0 == a.0`, sort `b.1` descending
        .then_with(|| b.2.cmp(&a.2))  // 3. If `b.1 == a.1`, sort `b.2` descending
        .then_with(|| a.3.cmp(&b.3))  // 4. If `b.2 == a.2`, sort `a.3` ascending
});
// Output
// [
//     (10, 2, 3, 1),
//     (10, 2, 3, 4),
//     (10, 2, 1, 3),
//     (10, 1, 5, 3),
//     (5, 8, 9, 2),
// ]

/// Binary Search
/// Reference -- https://doc.rust-lang.org/std/primitive.slice.html#method.binary_search
/// Case 1. locate the position of the given *value*
///      0  1  2  3  4  5  6  7  8  9   10  11  12
let s = [0, 1, 1, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55];
let low = s.partition_point(|x| x < &1);
assert_eq!(low, 1);
let high = s.partition_point(|x| x <= &1);
assert_eq!(high, 5);
let r = s.binary_search(&1);
assert!((low..high).contains(&r.unwrap()));
/// Case 2. Not Found
assert_eq!(s.partition_point(|x| x < &11), 9);
assert_eq!(s.partition_point(|x| x <= &11), 9);
assert_eq!(s.binary_search(&11), Err(9));
/// Case 3. Customised condition
let s = [0, 1, 1, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55];
let seek = 13;
assert_eq!(s.binary_search_by(|probe| probe.cmp(&seek)), Ok(9));
/// typical usage
let ss = [0, 1, 1, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55];
let target = 4;
let the_index = ss.binary_search(&target).unwrap_or_else(|insert_index| insert_index);
//if not found can be inserted into
ss.insert(the_index, 4);

/// Match tips
// match with the condition
#[allow(dead_code)]
enum Temperature {
    Celsius(i32),
    Fahrenheit(i32),
}
let temperature = Temperature::Celsius(35);
match temperature {
    Temperature::Celsius(t) if t > 30 => println!("{}C is above 30 Celsius", t),
    // The `if condition` part ^ is a guard
    Temperature::Celsius(t) => println!("{}C is equal to or below 30 Celsius", t),
    Temperature::Fahrenheit(t) if t > 86 => println!("{}F is above 86 Fahrenheit", t),
    Temperature::Fahrenheit(t) => println!("{}F is equal to or below 86 Fahrenheit", t),
}
// match with binding
let age = 15;
match age() {
    0             => println!("I haven't celebrated my first birthday yet"),
    n @ 1  ..= 12 => println!("I'm a child of age {:?}", n),
    n @ 13 ..= 19 => println!("I'm a teen of age {:?}", n),
    n             => println!("I'm an old person of age {:?}", n),
}
// >>> Tell me what type of person you are
// >>> I'm a teen of age 15
let some_num = Some(42);
match some_number() {
    Some(n @ 42) => println!("The Answer: {}!", n),
    Some(n)      => println!("Not interesting... {}", n),
    _            => (),
}
// >>> The Answer: 42!

// let ... else early return
fn dfs(head: &Option<Box<ListNode>>) -> bool {
    // this is called let ... else syntax for early return, which is valid after Rust 1.65
    // reference -- https://doc.rust-lang.org/rust-by-example/flow_control/let_else.html
    let Some(hh) = head else { return true };
    false
}

/// For loop
///
/// 1. loop for reference reading
/// for name in names.iter()
/// 2. loop and then consume the data source, meaning that elements are moved out from the array
/// for name in names.into_iter()
/// 3. loop for mut reference, so that we can modify the element directly
/// for name in names.iter_mut()

Algorithms

The backbone is based on the interview crash course, Data Structures and Algorithms, from LeetCode. Yes, I recommend this course when you are busy at work and you need a proper way to learn algorithm gradually. Like I said, this is a post about the techniques and remiders to myself. Therefore, it might look incoherent to you unlike the course.

But, I will list many useful techniques and why I failed to solve some problems and what was I thinking atm.

Array and strings

Array

String

// turn string
let chs = vec!['h', 'e', 'l', 'l', 'o'];
let s = chs.into_iter().collect::<String>();
let bs = vec![b'h', b'e', b'l', b'l', b'o'];
let s = String::from_utf8(bs);

// in-replace for the extended String
let mut bytes = s.into_bytes();
let mut chunk_end_i = bytes.len();
bytes.extend(std::iter::repeat(0).take(spaces.len()));

for (i, space) in spaces.into_iter().enumerate().rev() {
    let space = space as usize;
    bytes.copy_within(space..chunk_end_i, space + i + 1);
    bytes[space + i] = b' ';
    chunk_end_i = space;
}

unsafe { String::from_utf8_unchecked(bytes) }

Linked List

In this section, it is difficult when using Rust because there are more restrictions placed on flow manipulation.

/// Exalpme Problem - https://leetcode.com/problems/add-two-numbers/description/
pub fn exec_function(l1: Option<Box<ListNode>>, l2: Option<Box<ListNode>>) -> Option<Box<ListNode>> {
    // Initialize a dummy node to act as the base of the resulting list
    let mut base = ListNode { val: 0, next: None };
    // Get a mutable reference to the dummy node to act as our cursor
    let mut cursor = &mut base;

    // Use `as_ref` to get references to the inner data of `Option<Box<ListNode>>`.
    // Without `as_ref`, unwrapping would consume the `Option` and move its value,
    // which we don't want as we only need references.
    let mut cur_l1 = l1.as_ref();
    let mut cur_l2 = l2.as_ref();
    let mut carry = 0i32;

    // Iterate as long as there are nodes in either list or a carry remains
    while cur_l1.is_some() || cur_l2.is_some() || carry > 0 {
        let mut next_val = carry; // Start with the carry from the previous operation
        if let Some(node) = cur_l1 {
            next_val += node.val; // Add the value from the current node in `l1`
            cur_l1 = node.next.as_ref(); // Move to the next node in `l1`
        }
        if let Some(node) = cur_l2 {
            next_val += node.val; // Add the value from the current node in `l2`
            cur_l2 = node.next.as_ref(); // Move to the next node in `l2`
        }

        // Create a new node for the current sum (mod 10)
        let the_node = ListNode { val: next_val % 10, next: None };
        carry = next_val / 10; // Update the carry for the next iteration

        // Assign the new node to `cursor.next` and move the cursor forward
        cursor.next = Some(Box::new(the_node));
        cursor = cursor.next.as_mut().unwrap(); // Advance the cursor to the newly added node
    }

    base.next // Return the resulting list, skipping the dummy node
}