Rust Bytes Challenges

Welcome to Rust Bytes Challenges, a curated collection of coding challenges and Rust tips originally shared in the Rust Bytes newsletter.

These challenges, tips, and exercises are designed to help rustaceans get comfortable reading and writing Rust.

If you’re new to Rust, I would suggest starting with the Rust Book or Rustlings first, and then returning to these challenges.

If you have basic Rust understanding, then it’s time to flex your muscles by solving these challenges and putting your skills to the test.

About Rust Bytes

Rust Bytes is a curated bi-weekly newsletter keeping you informed about all things noteworthy in the world of The Rust Programming Language.

Expect to receive it in your inbox once a week: Mondays - all by 0:10 AM!

Here’s what’s in it for you:

  • Main discussion on a key Rust topic.
  • Rust Challenge/Tip to test your Rust skills.
  • Project Spotlight to showcase a remarkable open-source project written in Rust. This could be a crate, or tool pushing the boundaries of the language.
  • Ten (10) best handpicked links to articles, talks, podcasts and more.

You can join Rust Bytes.

Authors

This guide is maintained and updated by the team behind Rust Bytes.

  • John Philip. Software Dev/ Writer
  • Elley Duhe. Senior Writer/Editor

Support Rust Bytes

You’re Rust Bytes biggest fans, and we love to see it.

Here’s how you can help spread the word:

We hope this collection inspires you to keep learning and practicing, and ultimately become a better Rustacean.

Getting Started

Challenge Format

Each challenge consists of:

  • Problem Statement. A description of the task to solve.
  • Test Cases. To check if your solution works as expected.

Solutions/Explanations will be added soon!

Rust Tip

We aim to balance the challenges by sharing Rust tips in between.

You’ll find some Rust tips here and there that might impress you or prove genuinely useful.

Features

  • You can run Rust code, edit the code, and execute it directly within the platform.
  • Each challenge comes with built-in tests to check the validity of your solution.

Challenges Timeline

This collection currently includes challenges from the period December 18, 2023, to February 24, 2025.

As new challenges are released in each newsletter issue, they will be added to this collection.

Contribution

We welcome contributions of any kind.

Feel free to submit a Pull Request or report an issue on GitHub to get involved.

Challenge 1

Spot the Bug: Trait Trickery

Identify why the code fails to compile and suggest a way to fix it while keeping the trait object.


trait MyTrait {
    fn print(&self);
}

struct MyStruct;

impl MyTrait for MyStruct {
    fn print(&self) {
        println!("Hello from MyStruct");
    }
}

fn main() {
    let my_trait: MyTrait = MyStruct;
    
    my_traint.print();
}

Solution

Click to Show/Hide Solution

Explanation:

The code fails to compile because MyTrait is a trait object, and trait objects cannot be sized at compile time. Therefore, they must be used behind a reference (e.g., &dyn MyTrait) or a pointer (e.g., Box).

Solution:

To fix this, either change MyTrait to Box in the main function, or create a reference to MyStruct like let my_trait: &dyn MyTrait = &MyStruct;.

Challenge 2

Spot the Bug: Lifetime Trickery

Identify why the code fails to compile and suggest a way to fix it.


pub fn greater_of(first: &str, second: &str) -> &str {

    if first.len() > second.len() {
        return first;
    }

    second
}

fn main() {
    let first = "Rust";
    let second = "Bytes";

    println!("{}", greater_of(first, second));
}

Solution

Click to Show/Hide Solution

Explanation:

The function greater_of_two takes two string slices (&str) and returns the one with greater length. &str is an immutable reference to a sequence of UTF-8 text owned by someone else. This means &str (/string slice) has a definite lifetime: the stretch of the code for which a reference is valid.

The return type of the function is also a reference, which means Rust needs a way to define an association between the lifetimes of the input parameters and the lifetime of the return.

Solution:

To fix the compilation error, we need to use the lifetime annotation in the function greater_of_two.

#![allow(unused)]

fn main() {
fn greater_of_two<'a>(one: &'a str, other: &'a str) -> &'a str {
    if one.len() > other.len() {
        return one;
    }
    return other;
}
}

The function signature means that for some lifetime ’a, the input parameters live atleast as long as ’a and the returned reference will also live at least as long as the lifetime ’a.

Challenge 3

Spot the Bug: The Curious Case of the Missing Mutability:

Identify why the code fails to compile and suggest a way to fix it.


struct Tally {
    count: i32,
}

impl Tally {
    fn add_one(&self) {
        self.count += 1;
    }
}

fn main() {}

Solution

Click to Show/Hide Solution

Explanation:

The Bug: Compiler error! You can’t modify self.count within an &self method.

Taking &self is an immutable borrow. Methods with only &self promise not to change the underlying structure.

Solution:

Make the method take &mut self to allow modification:

#![allow(unused)]
fn main() {
fn add(&mut self) {
    self.count += 1; 
}
}

Challenge 4

Spot the Bug: The Curious Case of the ownership.

Identify why the code fails to compile and suggest a way to fix it.


use std::thread;
use std::thread::JoinHandle;

fn main() {
    let numbers = vec![1, 2, 3];
    let handle: JoinHandle<i32> = thread::spawn(|| {
        return numbers.iter().sum();
    });

    let sum = handle.join().unwrap();
    println!("Sum of numbers = {}", sum)
}

Solution

Click to Show/Hide Solution Explanation:

The Bug: Compiler error!

The spawn method is generic over two parameters F and T, where the generic parameter F defines the input type and T defines the return type. We will focus on the type parameter F, it is a closure that can be executed once and returns a result of type T.

pub fn spawn<F, T>(f: F) -> JoinHandle where F: FnOnce() -> T, F: Send + ’static, T: Send + ’static, { ….. }

Look at the lifetime of the input parameter type, it is ’static, F: Send + ’static.

This means F and any variables captured by F may outlive the function in which the spawn method is invoked. Hence, any variables captured by F should be owned by F.

Solution:

The main function needs to give away the ownership of the vector (numbers) by using the “move” keyword alongside the closure (in the invocation of spawn method).

fn main() {
    let numbers = vec![1,2,3,4];
    let handle: JoinHandle<i32> = thread::spawn(move || {
        return numbers.iter().sum();
    });
    let sum = handle.join().unwrap();
    println!("sum of numbers = {}", sum)
}

Challenge 5

Spot the Bug: The Curious Case of the allocation.

Identify why the code fails to compile and suggest a way to fix it.


#[derive(Debug)]
enum LinkedListNode {
    Empty,
    NonEmpty(i32, LinkedListNode),
}

fn main() {
    let node = LinkedListNode::NonEmpty(45, LinkedListNode::NonEmpty(50, LinkedListNode::Empty));

    let sum = handle.join().unwrap();
    println!("{:?}", node)
}

Solution

Click to Show/Hide Solution

Explanation:

The Bug: Compiler error!

The enum LinkedListNode contains two variants: Empty and NonEmpty.

The enum variant NonEmpty contains: i32 and LinkedListNode as its data. Rust can allocate the enum on stack, if its size is known at compile time, else users must make the heap allocation explicit.

To calculate the size of an enum, Rust compiler goes through all the variants and identifies the maximum size needed for a variant.

With LinkedListNode, Rust compiler checks the size needed for the variant Empty. It does not contain any data, so the compiler moves ahead to calculate the size of the variant NonEmpty. The size needed for the NonEmpty variant = sizeOf(i32) + sizeOf(LinkedListNode).

The definition of the enum is recursive and Rust can not calculate its size. So, the Rust compiler gives an error: “recursive type LinkedListNode has infinite size, recursive without indirection”.

Solution:

To solve the issue, we must change the definition of the enum to allow Rust compiler to calculate its size. As the compiler suggested, we must introduce an indirection in the enum variant NonEmpty. One way to do that is to use Box instead of LinkedListNode.

Box is pointer that uniquely owns the heap allocation of type T.

#[derive(Debug)]
enum LinkedListNode {
    Empty,
    NonEmpty(i32, Box<LinkedListNode>),
}

fn main() {
    let node = LinkedListNode::NonEmpty(
        45,
        Box::new(LinkedListNode::NonEmpty(
            50,
            Box::new(LinkedListNode::Empty)
        )),
    );
    println!("{:?}", node)
}

With this change, Rust compiler can now calculate the size of the enum variant NonEmpty: sizeOf(i32) + sizeOf(Box pointer), which is 4 bytes for i32 + 8 bytes for a Box pointer on a 64 bit machine + padding. 

Challenge 6

Spot the Bug: Iterator Sum Mystery.

Identify why the code fails to compile and suggest a way to fix it


fn main() {
    let vec = vec![1, 2, 3];

    let sum = vec.iter().map(|x| x * 2).sum();
    println!("Sum {}", sum)
}

Solution

Click to Show/Hide Solution

Explanation:

The Bug: type annotations needed.

The compiler encounters a situation where it can’t infer the type of the variable sum. This often happens when using methods like sum() on iterators. In this case, the sum() method requires the type to implement the Sum trait, but the compiler isn’t sure which specific type sum should be.

Solution:

Explicitly specify the type:

fn main() {
    let v = vec![1, 2, 3];
    let sum = v.iter().map(|x| x * 2).sum::<i32>();
    println!("Sum: {}", sum);
}

Challenge 7

Spot the bug

Identify why the code fails to compile and suggest a way to fix it.

#![allow(unused)]

fn main() {
use std::collections::HashMap;

struct UInt64Counter<Key> {
    count_by_key: HashMap<Key, u64>,
}

impl<Key> UInt64Counter<Key> {
    fn new() -> Self {
        UInt64Counter {
            count_by_key: HashMap::new(),
        }
    }

    fn add(&mut self, key: Key, delta: u64) {
        *self.count_by_key.entry(key).or_default() += delta;
    }
}
}

Solution

Click to Show/Hide Solution

The Bug: Compiler error!

Solution:

The struct UInt64Counter is generic over the type parameter Key which is used as the HashMap key.

#![allow(unused)]
fn main() {
struct UInt64Counter<Key> {
    count_by_key: HashMap<Key, u64>
}
}

The add method increments the counter for the provided key using the entry method of the HashMap.

#![allow(unused)]
fn main() {
fn add(&mut self, key: Key, delta: u64) {
    *self.count_by_key.entry(key).or_default() += delta;
}
}

If we look at the entry method of the HashMap, it is defined with with a few generic type parameters and one of them is the parameter K, K: Hash+Eq. This parameter is the key of the HashMap.

#![allow(unused)]
fn main() {
impl<K, V, S> HashMap<K, V, S>
where
    K: Eq + Hash,
    S: BuildHasher,
{
    pub fn entry(&mut self, key: K) -> Entry<'_, K, V> {
        map_entry(self.base.rustc_entry(key))
    }
}
}

This constraint means that the entry method exists for the HashMap if the key implements Eq and Hash traits. Hence, the compiler gives the error: “the method entry exists for struct HashMap<Key, u64>, but its trait bounds were not satisfied”

To solve the compilation error, we must use the constraint Eq + Hash in the generic parameter of UInt64Counter.

#![allow(unused)]
fn main() {
struct UInt64Counter<Key: Eq + Hash> {
    count_by_key: HashMap<Key, u64>,
}

impl<Key: Eq + Hash> UInt64Counter<Key> {
    fn new() -> Self {
        return UInt64Counter {
            count_by_key: HashMap::new()
        };
    }

    fn add(&mut self, key: Key, delta: u64) {
        *self.count_by_key.entry(key).or_default() += delta;
    }
}
}

Challenge 8

Ownership + Move + Borrow.

Identify why the code fails to compile and suggest a way to fix it.


fn main() {
    let marks = vec![10, 9, 8, 4, 6];
    let mut sum = 0;

    for mark in marks {
        sum = sum + mark;
    }
    println!("Sum of all marks: {:?} is {}", marks, sum);
}

Solution

Click to Show/Hide Solution

The Bug: Compiler error!

Solution:

marks has type Vec<i32>, which does not implement the Copy trait. This vector is moved in the for-loop due to an implicit call to .into_iter(). Another way of saying this is, the for-loop takes the ownership of the vector.

The println! tries to use the vector after it is already moved. So, the compiler gives the error: “value borrowed in println! after move”.

To solve the error we need to use a shared reference to the vector in the for-loop.

fn main() {
    let marks = vec![10, 9, 8, 4, 6];
    let mut sum = 0;
    for mark in &marks {
        sum = sum + mark;
    }
    println!("Sum of all marks {:?} is {}", marks, sum)
}

Challenge 9

Spot the Bug

Identify why the code fails to compile and suggest a way to fix it.


trait MathOperation {
    fn operate(&self, x: i32, y: i32) -> i32;
}

struct Addition;

impl MathOperation for Addition {
    fn operate(&self, x: i32, y: i32) -> i32 {
        x + y
    }
}

fn perform_operation(op: &MathOperation, x: i32, y: i32) -> i32 {
    op.operate(x, y)
}

fn main() {
    let add_op = Addition;

    let result = perform_operation(&add_op, 10, 5);

    println!("Addition result: {}", result);
}

Solution

Click to Show/Hide Solution

The Bug: Compiler error!

The bug lies in the perform_operation function and how it interacts with the MathOperation trait.

Solution:

The function takes a reference (&) to the MathOperation trait. This creates a reference to the trait definition itself, not a reference to a specific type that implements the trait. The compiler requires us to use the dyn keyword to explicitly declare a dynamically sized trait object.

Why dyn is Necessary:

Specifying dyn is necessary because trait objects are dynamically sized. They can hold a reference to any type that implements the MathOperation trait. The actual type is only determined at runtime. Without dyn, the compiler cannot determine the size of the data in the trait object, making it impossible to call methods through it.

trait MathOperation {
    fn operate(&self, x: i32, y: i32) -> i32;
}

struct Addition;
impl MathOperation for Addition {
    fn operate(&self, x: i32, y: i32) -> i32 {
        x + y
    }
}

fn main() {
    let add_op = Addition;

    let result = perform_operation(&add_op, 10, 5);

    println!("Addition result: {}", result);
}

fn perform_operation(op: &dyn MathOperation, x: i32, y: i32) -> i32 {
    op.operate(x, y)
}

Challenge 10

Spot the Bug

Identify why the code fails to compile and suggest a way to fix it.


fn main() {
    let mut numbers = vec![1, 2, 3, 4, 5];

    for num in &numbers {
        numbers.push(num * 2);
    }

    println!("Doubled numbers: {:?}", numbers);
}

Solution

Click to Show/Hide Solution

The Bug: Compiler error!

The bug in the code is that it attempts to mutate the numbers vector while iterating over it immutably. In Rust, when you iterate over a collection using a borrowed reference (such as &numbers), you cannot mutate the collection. Therefore, attempting to push elements into the numbers vector inside the loop causes a compilation error because it violates Rust’s borrowing rules.

Solution:

fn main() {
    let mut numbers = vec![1, 2, 3, 4, 5];
    let len = numbers.len();
    
    for _ in 0..len {
        numbers.push(numbers[numbers.len() - len] * 2);
    }
    
    println!("Doubled numbers: {:?}", numbers);
}

In this corrected version, we first store the length of the numbers vector in a variable len. Then, we iterate over the range 0..len, which ensures that the loop runs for the original length of the vector. Inside the loop, we push the doubled value of each element from the original numbers vector.

Challenge 11

Spot the Bug

Identify why the code fails to compile and suggest a way to fix it.


fn factorial(n: u32) -> u32 {
    if n == 0 { 1 } else { factorial(n) }
}

fn main() {
    let result = factorial(5);
    
    println!("Factorial of 5: {}", result);
}

Solution

Click to Show/Hide Solution

The Bug: Infinite Recursion

The factorial function recursively calls itself but forgets to multiply the result by n - 1 in the recursive case, leading to infinite recursion.

Solution:

Add the multiplication by n within the recursive case:

#![allow(unused)]
fn main() {
fn factorial(n: u32) -> u32 {
  if n == 0 {
    1
  } else {
    n * factorial(n - 1)
  }
}

}

Challenge 12

Spot the Bug

Identify why the code fails to compile and suggest a way to fix it.

trait Animal {
    fn make_sound(&self);
}

struct Dog {
    name: String,
}

impl Animal for Dog {
    fn make_sound(&self) {
        println!("{} says: Woof!", self.name);
    }
}

struct Cat {
    name: String,
}

impl Animal for Cat {
    fn make_sound(&self) {
        println!("{} says: Meow!", self.name);
    }
}

fn main() {
    let dog = Dog {
        name: String::from("Rex"),
    };
    let cat = Cat {
        name: String::from("Lisa"),
    };

    let animals: Vec<dyn Animal> = vec![dog, cat];

    for animal in animals {
        animal.make_sound();
    }
}

Solution

Click to Show/Hide Solution

The Bug:

The bug in the code is due to the attempt to create a vector of trait objects (dyn Animal) directly from instances of concrete types (Dog and Cat).

This is not possible because trait objects have a dynamic size that is not known at compile time.

To fix the bug, you need to box the instances of Dog and Cat before adding them to the vector.

Solution:

#![allow(unused)]
fn main() {
  let animals: Vec<Box<dyn Animal>> = vec![Box::new(dog),Box::new(cat)];
}

In the corrected code, dog and cat are boxed (Box::new(dog) and Box::new(cat)) before being added to the vector. This allows them to be treated as trait objects with a known size at compile time.

Challenge 13

Rust Quiz

What will be the output?


fn main() {
    let mut numbers = vec![1, 2, 3];
    numbers.resize(5, 2);

    println!("{:?}", numbers);
}

Which of the following is the correct output of the code?

A. [1, 2, 3, 5, 5]
B. [1, 2, 3, 2, 2]
C. [1, 2, 3, 0, 0]

Solution

Click to Show/Hide Solution

Correct Answer:

B. [1, 2, 3, 2, 2]

The resize is a method available on the Vec type in Rust. It allows you to dynamically adjust the size of a vector by specifying the desired new length and the value to fill in any newly added elements.

The resize method takes two arguments:

  1. New Length: This specifies the desired number of elements in the vector after resizing. In this case, it’s set to 5.
  2. Value: This specifies the value to fill in the newly added elements. Here, it’s set to 2.

In our scenario above resize adds the specified number of elements 5 with the provided value 2 to the end of the vector.

Since the original vector has 3 elements, resize adds two new elements with the value 2, resulting in the final vector: [1, 2, 3, 2, 2].

Challenge 14

Rust Quiz

What will be the output?


fn main() {
    let mut rust_bytes = String::from("Rust Bytes");
    let rust_bytes_ref = &mut rust_bytes;

    rust_bytes.push_str(" is great.");

    println!("{}", rust_bytes_ref);
}
  • A. Rust Bytes is great.
  • B. Compiler Error

Solution

Click to Show/Hide Solution Correct Answer:

B. Compilation Error.

rust_bytes_ref is a mutable reference to the String rust_bytes. This is the first mutable reference to the String type.

Let’s look at the signature of push_str method:

#![allow(unused)]
fn main() {
pub fn push_str(&mut self, string: &str) {
    self.vec.extend_from_slice(string.as_bytes())
} 
}

This method needs a mutable reference. So, the invocation of this method results in another mutable reference to the same string

Rust does not allow multiple mutable references to a type in the same scope.

Solutions:

Option 1:

Remove the mutable reference rust_bytes_ref.

Option 2:

Restructure the code and create a separate method that takes the mutable reference and pushes into the string.

fn main() {
    let mut rust_bytes = String::from("Rust Bytes");
    push(&mut rust_bytes);

    let rust_bytes_ref = &mut rust_bytes;
    println!("{}", rust_bytes_ref);
}

fn push(str: &mut String) {
    str.push_str(" is great.");
}

Challenge 15

Rust Quiz

How many times will the message Value not found be printed?


fn main() {
    let my_vector = vec![Some("Rust"), None, Some("Bytes"), None];

    for element in my_vector.iter() {
        let value = element.unwrap_or({
            println!("Value not found");
            "None"
        });
    }
}

A. 2 (times)

B. 4 (times)

C. 1

Solution

Click to Show/Hide Solution

Correct Answer:

B. 4 (times)

The key to understanding the behavior here is unwrap_or's eagerness.

Unlike what it might seem, it executes the provided closure in all cases, regardless of whether the Option value is Some or None.

For Some values the closure is executed, but the existing value within Some is returned.

This might seem unnecessary, but it’s important for consistency and potential side effects within the closure.

For None values the closure is crucial. It’s executed to provide a default value and often performs side effects like printing the message “Value not found!” in this example.

Challenge 16

Rust Quiz

What is the output of this Rust program?


fn main() {
    let (.., x, y) = (0, 1, ..);

    println!("{:}", b"066"[y][x]);
}

A. The program does not compile

B. The program prints 54

C. The program is guaranteed to output: []

Solution

Click to Show/Hide Solution

This question demonstrates two different meanings of ..

In expression position, .. is the syntax for constructing various types of ranges. Here the expression (0, 1, ..) is a tuple with three elements, the third one having type RangeFull.

On the other hand in a pattern, .. is used to mean “any number of elements”. So the pattern (.., x, y) matches a tuple with 2 or more elements, binding the second-last one to x and the last one to y.

Coming out of the first line of main, we have x = 1 and y = (..). Thus the value printed is going to be b“066“[..][1].

The expression b“066“ is a byte-string literal of type &‘static [u8; 3] containing the three ASCII bytes b’0’, b’6’, b’6’.

When we slice the byte-string with RangeFull we get a dynamically sized slice [u8] of length 3. Next we access element 1 of the slice, which is the byte b’6’ of type u8. When printed, we see the decimal representation of the byte value of the ASCII digit 6, which is the number 54.

You can play with the code on Rust Playground.

Attribution: Challenge courtesy of dtolnay, including the insightful explanation!

Challenge 17

Rust Quiz

What is the output of this Rust program?

fn main() {
    let mut y = 6;

    --y;

    println!("{}{}", --y, --y);
}

A. The program does not compile

B. The program outputs: 44

C. The program outputs: 66

Solution

Click to Show/Hide Solution

Answer

C. The program outputs: 66

Why? Unlike some languages, Rust doesn’t have Unary operators for incrementing or decrementing variables. Rust deliberately avoids unary increment (++) and decrement (–) operators for several reasons:

Complexity: Unary operators can be confusing due to their dependence on evaluation order. It can be unclear whether the decrement happens before or after the value is used.

Clarity: Explicit expressions like (y -= 1) or (y = y + 1) are more readable and less prone to errors.

Alternatives: Rust often promotes iterators and functional programming approaches, which can achieve similar results without these operators.

Alternative Approach:

Here’s how you can achieve a similar outcome in Rust using explicit decrements:

fn main() {
  let mut y = 6;
  y -= 1;

  println!("{}{}", y, y);
}

This approach is more explicit and avoids the potential confusion of unary operators.

Challenge 18

Rust Tip: Flatten Your Options with Confidence

When working with nested Option types in Rust, it’s common to encounter structures like Option<Option<&str>>.

Flattening such nested structure into a single Option<&str> helps simplify your code and avoid unnecessary nesting.


fn main() {
    let maybe_nested_message: Option<Option<&str>> = Some(Some("Rust Bytes is Awesome!"));

    // Flatten the nested option to get a single Option<&str>
    let message_option = maybe_nested_message.flatten();

    // Print the message or a default value if None
    println!("{:?}", message_option.unwrap_or("Unknown"));
}

Challenge 19

Rust Tip: Unwrapping Multiple Options in Rust

In Rust, the Option is a data type that can hold a value (Some(value)) or indicate its absence (None).

The code snippet below showcases a common pattern for handling multiple Option values simultaneously using pattern matching in an (if let) expression. Here’s a breakdown:

Two Option variables (name1 and name2) are declared and assigned Some values, representing existing data.

An if let expression is used to perform pattern matching on both name1 and name2.

The pattern (Some(name1), Some(name2)) checks if both name1 and name2 are indeed Some variants and binds the contained values to temporary variables name1 and name2 within the code block.


fn main() {
    let name1 = Some("Rust");
    let name2 = Some("Bytes");

    if let (Some(name1), Some(name2)) = (name1, name2) {
        println!(
            "Name1 is ({:?}) and Name2 is ({:?})",
            name1, name2
        );
    } else {
        println!("Either one or both values are None");
    }
}

Challenge 20

Rust Quiz

What is the output of this Rust program?

trait Printable {
    fn print_value(&self);
}

impl Printable for (u32, u32) {
    fn print_value(&self) {
        print!("1");
    }
}

impl Printable for (i32, i32) {
    fn print_value(&self) {
        print!("2");
    }
}

fn main() {
    (0, 0).print_value();
    (0, 0).print_value();
}

A. The program does not compile

B. The program outputs: 22

C. The program outputs: 11

Solution

Click to Show/Hide Solution

Answer

B. The program outputs: 22

Why?

The code will print “22” due to a combination of Rust’s type inference, default integral type behaviour, and trait resolution mechanisms.

Rust infers types automatically based on context. In our case, both (0, 0) and (0, 0,) are inferred as tuples of type (i32, i32)

The trailing comma in (0, 0,) doesn’t affect the type here because it’s a 2-tuple, and trailing commas are optional for tuples with more than one element.

Trait Resolution and Ambiguity Preference

In the code we defined a trait Printable with implementations for tuples of type (u32, u32) and (i32, i32).

When calling print_value on both tuples, Rust needs to find a matching implementation. Since both tuples are inferred as (i32, i32), there’s a potential ambiguity.

However, Rust prioritizes the default integral type (i32) during trait resolution. As a result, the implementation for (i32, i32) is chosen in both cases, leading to “2” being printed twice.

Challenge 21

Rust Tip: handling errors gracefully

Avoid using unwrap and expect in production code unless you’re certain the Option or Result will not be None or Err. Instead, handle potential errors gracefully.

fn main(){
    let result: Result<i32, &str> = Ok(10);

    match result {
        Ok(value) => println!("Value: {}", value),
        Err(e) => println!("Error: {}", e),
    }
}

Challenge 22

Rust Tip: Measure Function Execution Time

Want to see the time your code takes to finish running? Rust’s built-in std::time::Instant module lets us measure the elapsed time for any piece of code, making it easy to identify performance bottlenecks.


use std::time::Instant;

fn expensive_computation(n: u32) -> u32 {
    let mut sum = 0;
    for i in 0..n {
        sum += i;
    }
    sum
}

fn main() {
    // Measure elapsed time
    let start = Instant::now();
    expensive_computation(10000);
    let elapsed = start.elapsed();

    println!("Time elapsed: {:?}", elapsed);
}

Challenge 23

Rust Quiz

What is the output of this Rust program?

trait Or {
    fn f(self);
}

struct T;

impl Or for &T {
    fn f(self) {
        print!("1");
    }
}

impl Or for &&&&&T {
    fn f(self) {
        print!("2");
    }
}

fn main() {
    let t = T;
    let wt = &T;
    let wwt = &&T;
    let wwwt = &&&T;
    let wwwwt = &&&&T;

    t.f();
    wt.f();
    wwt.f();
    wwwt.f();
    wwwwt.f();
}

A. The program exhibits undefined behavior.

B. The program does not compile.

C. The program is guaranteed to output 11112.

Solution

Click to Show/Hide Solution

Answer C. The program is guaranteed to output: 111222

The Reference describes Rust’s method lookup order. The relevant paragraph is:

Obtain [the candidate receiver type] by repeatedly dereferencing the receiver expression’s type, adding each type encountered to the list, then finally attempting an unsized coercion at the end, and adding the result type if that is successful.

Then, for each candidate T, add &T and &mut T to the list immediately after T.

Applying these rules to the given examples, we have:

t.f(): We try to find a function f defined on the type T, but there is none. Next, we search the type &T, and find the first implementation of the Or trait, and we are done. Upon invocation, the resolved call prints 1.

wt.f(): We search for a function f defined on &T, which immediately succeeds. Upon invocation, the function prints 1.

wwt.f(): The search order is &&T -> &&&T -> &mut &&T -> &T, and we’re done. Upon invocation, the function prints 1.

wwwt.f(): &&&T -> &&&&T. This prints 2.

wwwwt.f(): &&&&T. This prints 2.

wwwwwt.f(): &&&&&T -> &&&&&&T -> &mut &&&&&T -> &&&&T. This prints 2.

The challenge and solution is by David Tolnay.

Challenge 24

String Reversal

Write a function string_reversal that reverses a mutable string slice (&mut str) in-place without allocating new memory on the heap (no String::from or Vec::from).


fn string_reversal(s: &mut str) {
    // your implementation goes here
    // dont touch the code in the main function below
}

fn main() {
    // Test case 1: Basic reversal
    let mut s = String::from("Rust Bytes!");
    let ptr_before = s.as_ptr();
    {
        let s_slice: &mut str = &mut s;
        string_reversal(s_slice);
    }
    let ptr_after = s.as_ptr();
    if s != "!setyB tsuR" {
        panic!("Test failed: Expected '!setyB tsuR', got '{}'", s);
    }
    if ptr_before != ptr_after {
        panic!("Test failed: Memory location changed");
    }
    println!("Test 1 passed: s = '{}', memory location unchanged", s);

    // Test case 2: Empty string
    let mut empty = String::from("");
    let ptr_before = empty.as_ptr();
    {
        let s_slice: &mut str = &mut empty;
        string_reversal(s_slice);
    }
    let ptr_after = empty.as_ptr();
    if empty != "" {
        panic!("Test failed: Expected '', got '{}'", empty);
    }
    if ptr_before != ptr_after {
        panic!("Test failed: Memory location changed");
    }
    println!("Test 2 passed: empty = '{}', memory location unchanged", empty);

    // Test case 3: Single character
    let mut single = String::from("a");
    let ptr_before = single.as_ptr();
    {
        let s_slice: &mut str = &mut single;
        string_reversal(s_slice);
    }
    let ptr_after = single.as_ptr();
    if single != "a" {
        panic!("Test failed: Expected 'a', got '{}'", single);
    }
    if ptr_before != ptr_after {
        panic!("Test failed: Memory location changed");
    }
    println!("Test 3 passed: single = '{}', memory location unchanged", single);
}

Solution

Click to Show/Hide Solution
#![allow(unused)]
fn main() {
fn string_reversal(s: &mut str) {
    let bytes = unsafe { s.as_bytes_mut() };
    let (mut left, mut right) = (0, bytes.len().saturating_sub(1));

    while left < right {
        bytes.swap(left, right);
        left += 1;
        right -= 1;
    }
}
}

Challenge 25

IP Validation

Write a function is_valid_ip that will verfify valid IPv4 addresses in dot-decimal format. IPs should be considered valid if they consist of four octets, with values between 0 and 255, inclusive.

fn is_valid_ip(ip: &str) -> bool {
    // your implementation goes here
    // dont touch the code in the main function below
}

fn main() {
    // Test 1: Valid IP - Basic case
    let ip1 = "1.2.3.4";
    if !is_valid_ip(ip1) {
        panic!("Test 1 failed: '{}' should be valid", ip1);
    }
    println!("Test 1 passed: '{}' is valid", ip1);

    // Test 2: Valid IP - All numbers in range
    let ip2 = "123.45.67.89";
    if !is_valid_ip(ip2) {
        panic!("Test 2 failed: '{}' should be valid", ip2);
    }
    println!("Test 2 passed: '{}' is valid", ip2);

    // Test 3: Invalid IP - Too few octets
    let ip3 = "1.2.3";
    if is_valid_ip(ip3) {
        panic!("Test 3 failed: '{}' should be invalid (too few octets)", ip3);
    }
    println!("Test 3 passed: '{}' is invalid", ip3);

    // Test 4: Invalid IP - Too many octets
    let ip4 = "1.2.3.4.5";
    if is_valid_ip(ip4) {
        panic!("Test 4 failed: '{}' should be invalid (too many octets)", ip4);
    }
    println!("Test 4 passed: '{}' is invalid", ip4);

    // Test 5: Invalid IP - Out of range (456 > 255)
    let ip5 = "123.456.78.90";
    if is_valid_ip(ip5) {
        panic!("Test 5 failed: '{}' should be invalid (octet out of range)", ip5);
    }
    println!("Test 5 passed: '{}' is invalid", ip5);

    // Test 6: Invalid IP - Leading zeros
    let ip6 = "123.045.067.089";
    if is_valid_ip(ip6) {
        panic!("Test 6 failed: '{}' should be invalid (leading zeros)", ip6);
    }
    println!("Test 6 passed: '{}' is invalid", ip6);

    // Test 7: Invalid IP - Non-numeric octet
    let ip7 = "123.45.67.8a";
    if is_valid_ip(ip7) {
        panic!("Test 7 failed: '{}' should be invalid (non-numeric octet)", ip7);
    }
    println!("Test 7 passed: '{}' is invalid", ip7);

    // Test 8: Invalid IP - Empty octet
    let ip8 = "123.45..89";
    if is_valid_ip(ip8) {
        panic!("Test 8 failed: '{}' should be invalid (empty octet)", ip8);
    }
    println!("Test 8 passed: '{}' is invalid", ip8);

    // Test 9: Edge case - All zeros
    let ip9 = "0.0.0.0";
    if !is_valid_ip(ip9) {
        panic!("Test 9 failed: '{}' should be valid", ip9);
    }
    println!("Test 9 passed: '{}' is valid", ip9);

    // Test 10: Edge case - Maximum values
    let ip10 = "255.255.255.255";
    if !is_valid_ip(ip10) {
        panic!("Test 10 failed: '{}' should be valid", ip10);
    }
    println!("Test 10 passed: '{}' is valid", ip10);
}

Solution

Click to Show/Hide Solution
#![allow(unused)]
fn main() {
fn is_valid_ip(ip: &str) -> bool {
    let octets: Vec<&str> = ip.split('.').collect();
    if octets.len() != 4 {
        return false;
    }
    for octet in octets {
        if octet.is_empty() {
            return false;
        }
        if octet.len() > 1 && octet.starts_with('0') {
            return false;
        }
        match octet.parse::<u8>() {
            Ok(_) => (),
            Err(_) => return false,
        }
    }
    true
}
}

or

#![allow(unused)]

fn main() {
use std::net::IpAddr;

fn is_valid_ipv4(ip: &str) -> bool {
    match ip.parse::<IpAddr>() {
        Ok(IpAddr::V4(_)) => true,
        _ => false,
    }
}
}

Challenge 26

Rust Tip: Manage Environment Variables Directly in Cargo.toml

While .env files are popular for configuration, Rust’s Cargo.toml offers a built-in way to define environment variables for your project.

This can be useful for several reasons:

  • Centralized management to keep environment variables alongside your project settings in Cargo.toml.
  • Integration to allow access to these variables directly in your build scripts.
  • You can avoid the need for separate .env files, especially in simpler projects.

Here’s an example usage of [env] in Cargo.toml

[env]
# Set an absolute path for OpenSSL
OPENSSL_DIR = "/opt/openssl"

# Forcefully override existing TMPDIR variable
TMPDIR = { value = "/home/tmp", force = true }

# Set a relative path based on project directory
OPENSSL_DIR = { value = "vendor/openssl", relative = true }
This configuration keeps your environment variables within your project and integrates nicely with Cargo's build system.

Challenge 27

Rust Challenge

Given two strings, a and b, return a string of the form short+long+short, with the shorter string on the outside and the longer string on the inside.

The strings will not be the same length, but they may be empty ( zero length ).

fn short_long_short(a: &str, b: &str) -> String {
    // your implementation goes here
    // dont touch the code in the main function below
}

fn main() {
    // Test 1: a is shorter ("1", "22") -> "1221"
    let result1 = short_long_short("1", "22");
    if result1 != "1221" {
        panic!("Test 1 failed: Expected '1221', got '{}'", result1);
    }
    println!("Test 1 passed: short_long_short(\"1\", \"22\") = '{}'", result1);

    // Test 2: b is shorter ("22", "1") -> "1221"
    let result2 = short_long_short("22", "1");
    if result2 != "1221" {
        panic!("Test 2 failed: Expected '1221', got '{}'", result2);
    }
    println!("Test 2 passed: short_long_short(\"22\", \"1\") = '{}'", result2);

    // Test 3: Empty string as shorter ("", "abc") -> "abc"
    let result3 = short_long_short("", "abc");
    if result3 != "abc" {
        panic!("Test 3 failed: Expected 'abc', got '{}'", result3);
    }
    println!("Test 3 passed: short_long_short(\"\", \"abc\") = '{}'", result3);

    // Test 4: Empty string as longer ("abc", "") -> "abc"
    let result4 = short_long_short("abc", "");
    if result4 != "abc" {
        panic!("Test 4 failed: Expected 'abc', got '{}'", result4);
    }
    println!("Test 4 passed: short_long_short(\"abc\", \"\") = '{}'", result4);

    // Test 5: Different lengths ("45", "1") -> "1451"
    let result5 = short_long_short("45", "1");
    if result5 != "1451" {
        panic!("Test 5 failed: Expected '1451', got '{}'", result5);
    }
    println!("Test 5 passed: short_long_short(\"45\", \"1\") = '{}'", result5);
}

Solution

Click to Show/Hide Solution
#![allow(unused)]
fn main() {
fn short_long_short(a: &str, b: &str) -> String {
    let (short, long) = if a.len() < b.len() { (a, b) } else { (b, a) };
    format!("{}{}{}", short, long, short)
}
}

Or

#![allow(unused)]
fn main() {
fn short_long_short(a: &str, b: &str) -> String {
    if a.len() < b.len() {
        format!("{}{}{}", a, b, a)
    } else {
        format!("{}{}{}", b, a, b)
    }
}
}

Challenge 28

Rust Challenge: GA-DE-RY-PO-LU-KI Cypher Challenge

Write encode and decode functions for the GA-DE-RY-PO-LU-KI substitution cypher, where letters are swapped based on the key “GA-DE-RY-PO-LU-KI” (e.g., G↔A, D↔E, etc.), case-sensitively, leaving unmapped characters unchanged.

fn encode(text: &str) -> String {
    // your implementation goes here
    // don't touch the code in the main function below
}

fn decode(s: &str) -> String {
    // your implementation goes here
    // don't touch the code in the main function below
}

fn main() {
    // Test 1: Encode ABCD -> GBCE
    let result1 = encode("ABCD");
    if result1 != "GBCE" {
        panic!("Test 1 failed: Expected 'GBCE', got '{}'", result1);
    }
    println!("Test 1 passed: encode(\"ABCD\") = '{}'", result1);

    // Test 2: Encode "Ala has a cat" -> "Gug hgs g cgt"
    let result2 = encode("Ala has a cat");
    if result2 != "Gug hgs g cgt" {
        panic!("Test 2 failed: Expected 'Gug hgs g cgt', got '{}'", result2);
    }
    println!("Test 2 passed: encode(\"Ala has a cat\") = '{}'", result2);

    // Test 3: Encode "gaderypoluki" -> "agedyropulik"
    let result3 = encode("gaderypoluki");
    if result3 != "agedyropulik" {
        panic!("Test 3 failed: Expected 'agedyropulik', got '{}'", result3);
    }
    println!("Test 3 passed: encode(\"gaderypoluki\") = '{}'", result3);

    // Test 4: Decode "Gug hgs g cgt" -> "Ala has a cat"
    let result4 = decode("Gug hgs g cgt");
    if result4 != "Ala has a cat" {
        panic!("Test 4 failed: Expected 'Ala has a cat', got '{}'", result4);
    }
    println!("Test 4 passed: decode(\"Gug hgs g cgt\") = '{}'", result4);

    // Test 5: Decode "agedyropulik" -> "gaderypoluki"
    let result5 = decode("agedyropulik");
    if result5 != "gaderypoluki" {
        panic!("Test 5 failed: Expected 'gaderypoluki', got '{}'", result5);
    }
    println!("Test 5 passed: decode(\"agedyropulik\") = '{}'", result5);

    // Test 6: Decode "GBCE" -> "ABCD"
    let result6 = decode("GBCE");
    if result6 != "ABCD" {
        panic!("Test 6 failed: Expected 'ABCD', got '{}'", result6);
    }
    println!("Test 6 passed: decode(\"GBCE\") = '{}'", result6);
}

Solution

Click to Show/Hide Solution
#![allow(unused)]

fn main() {
use std::collections::HashMap;

fn encode(text: &str) -> String {
    let cypher = "GADERYPOLUKIgaderypoluki";
    text.chars()
        .map(|c| {
            cypher
                .find(c)
                .map_or(c, |pos| cypher.as_bytes()[pos ^ 1] as char)
        })
        .collect()
}

fn decode(text: &str) -> String {
    encode(text) // Since the substitution is symmetric, decode is the same as encode
}
}

Challenge 29

Rust Tip: Zero-cost Abstractions with PhantomData for Marker Types

When dealing with advanced ownership patterns or marker traits, you might encounter situations where a type parameter needs to exist without actually being used at runtime.

Rust provides PhantomData for this purpose, and while it may seem esoteric, it can be incredibly useful for enforcing lifetimes or type constraints at compile-time without incurring any runtime cost.

Example: Enforcing Lifetimes Without Storing References

use std::marker::PhantomData;

struct MyStruct<'a, T> {
    value: i32,
    _marker: PhantomData<&'a T>, // PhantomData to tie `T` to lifetime 'a
}

impl<'a, T> MyStruct<'a, T> {
    fn new(value: i32) -> Self {
        MyStruct {
            value,
            _marker: PhantomData, // PhantomData is a zero-sized type
        }
    }

    fn get_value(&self) -> i32 {
        self.value
    }
}

fn main() {
    let s: MyStruct<'_, u8> = MyStruct::new(42); // 'u8' is purely compile-time
    println!("{}", s.get_value()); // Output: 42
}

Challenge 30

Rust Tip: Using zip() to Combine Iterators

When working with multiple collections in Rust, you often need to process corresponding elements together.

The zip() function is a powerful tool for combining two iterators into one, pairing their elements into tuples. This allows you to efficiently handle parallel data.

How It Works

The zip() function takes two iterators and produces an iterator of tuples.

It returns a new iterator that will iterate over two other iterators, returning a tuple where the first element comes from the first iterator, and the second element comes from the second iterator.

In other words, it zips two iterators together, into a single one. If either iterator returns None, next from the zipped iterator will return None.

If the first iterator returns None, zip will short-circuit and next will not be called on the second iterator.

Example:

fn main() {
    let a = vec![1, 2, 3];
    let b = vec![4, 5, 6];

    // consumes the vectors and creates 
    // iterators that yield owned values.
    let zipped: Vec<(i32, i32)> = a.into_iter()
        .zip(b.into_iter())
        .collect();

    println!("{:?}", zipped); // Output: [(1, 4), (2, 5), (3, 6)]
}

Challenge 31

Rust Challenge

Write a function abbreviate that converts phrases into acronyms.

Hyphens are treated as word separators, and all other punctuation should be removed.

For example:

  • Input: ‘World Health Organization’ → Output: ‘WHO’
  • Input: ‘National Aeronautics and Space Administration’ → Output: ‘NASA’
  • Input: ‘Self-Contained Underwater Breathing Apparatus’ → Output: ‘SCUBA’
  • Input: ‘Random Access Memory’ → Output: ‘RAM’
pub fn abbreviate(word: &str) -> String {
    // your implementation goes here
    // don't touch the code in the main function below
}

fn main() {
    let test1 = String::from("HyperText Markup Language");
    let test2 =
        String::from("Rolling On The Floor Laughing So Hard That My Dogs Came Over And Licked Me");

    assert_eq!(
        abbreviate(&test1),
        String::from("HTML"),
        "Test 1 Failed: {}",
        test1
    );
    println!("Test 1 Passed: Abbreviation for '{}' is 'HTML'", test1);

    assert_eq!(
        abbreviate(&test2),
        String::from("ROTFLSHTMDCOALM"),
        "Test 2 Failed: {}",
        test2
    );
    println!(
        "Test 2 Passed: Abbreviation for '{}' is 'ROTFLSHTMDCOALM'",
        test2
    );
}

Solution

Click to Show/Hide Solution
#![allow(unused)]
fn main() {
pub fn abbreviate(word: &str) -> String {
    word.split(|c: char| c.is_whitespace() || c == '-')
        .map(|word| acronym(word))
        .collect::<Vec<String>>()
        .join("")
}

fn acronym(word: &str) -> String {
    if word.to_uppercase() == word || word.to_lowercase() == word {
        match word.chars().nth(0) {
            Some(word) => word.to_string(),
            None => "".to_string(),
        }
    } else {
        word.chars()
            .filter(|c| c.is_uppercase())
            .collect::<String>()
    }
}
}

Challenge 32

Rust Challenge

Given a list of string slices, write a function count_a that counts how many occurrences of the letter a (case-sensitive) appear across all the strings in the list.

You should return the total count of as found.


fn count_a(strings: Vec<&str>) -> usize {
    // your implementation goes here
    // don't touch the code in the main function below
}

fn main() {
    // Test 1: Count 'a' as 7 in ["apple", "banana", "avocado", "grape"]
    let strings1 = vec!["apple", "banana", "avocado", "grape"];
    let result1 = count_a(strings1);
    if result1 != 7 {
        panic!("Test 1 failed: Expected 7, got {}", result1);
    }
    println!("Test 1 passed: count_a([\"apple\", \"banana\", \"avocado\", \"grape\"]) = {}", result1);

    // Test 2: Count 'a' as 52 in [50 'a's, "Apple", "banAna"]
    let a = "a".repeat(50);
    let long_strings = vec![&a, "Apple", "banAna"];
    let result2 = count_a(long_strings);
    if result2 != 52 {
        panic!("Test 2 failed: Expected 52, got {}", result2);
    }
    println!("Test 2 passed: count_a([50 'a's, \"Apple\", \"banAna\"]) = {}", result2);

    // Test 3: Count 'a' as 0 in ["berry", "kiwi", "plum"]
    let no_a_strings = vec!["berry", "kiwi", "plum"];
    let result3 = count_a(no_a_strings);
    if result3 != 0 {
        panic!("Test 3 failed: Expected 0, got {}", result3);
    }
    println!("Test 3 passed: count_a([\"berry\", \"kiwi\", \"plum\"]) = {}", result3);
}

Solution

Click to Show/Hide Solution
#![allow(unused)]

fn main() {
fn count_a(strings: Vec<&str>) -> usize {
    strings.iter()
        .map(|s| s.chars().filter(|&c| c == 'a').count())
        .sum()
}

}

Challenge 33

Rust Challenge: Calculate Hamming Distance

Write a function hamming_distance that calculates the Hamming Distance between two DNA strands (strings of C, A, G, T).

The Hamming Distance is the number of positions where the characters differ.

fn hamming_distance(dna1: &str, dna2: &str) -> Result<usize, &'static str> {
    // your implementation goes here
    // don't touch the code in the main function below
}

fn main() {
    // Helper function to map Result to Option for test compatibility
    let to_option = |result: Result<usize, &str>| match result {
        Ok(distance) => Some(distance),
        Err(_) => None,
    };

    // Test 1: GAGCCTACTAACGGGAT and CATCGTAATGACGGCCT -> 7
    let result1 = to_option(hamming_distance("GAGCCTACTAACGGGAT", "CATCGTAATGACGGCCT"));
    if result1 != Some(7) {
        panic!("Test 1 failed: Expected Some(7), got {:?}", result1);
    }
    println!("Test 1 passed: hamming_distance = {:?}", result1);

    // Test 2: Same length strings (from test_hamming_distance_same_length)
    let result2 = to_option(hamming_distance("AATG", "AAA"));
    if result2 != None {
        panic!("Test 2 failed: Expected None, got {:?}", result2);
    }
    println!("Test 2 passed: hamming_distance(\"AATG\", \"AAA\") = {:?}", result2);

    let result3 = to_option(hamming_distance("G", "T"));
    if result3 != Some(1) {
        panic!("Test 3 failed: Expected Some(1), got {:?}", result3);
    }
    println!("Test 3 passed: hamming_distance(\"G\", \"T\") = {:?}", result3);

    let result4 = to_option(hamming_distance("GGACGGATTCTG", "AGGACGGATTCT"));
    if result4 != Some(9) {
        panic!("Test 4 failed: Expected Some(9), got {:?}", result4);
    }
    println!("Test 4 passed: hamming_distance(\"GGACGGATTCTG\", \"AGGACGGATTCT\") = {:?}", result4);

    // Test 5: Different length strings (from test_hamming_distance_different_length)
    let result5 = to_option(hamming_distance("AATG", "AAAAA"));
    if result5 != None {
        panic!("Test 5 failed: Expected None, got {:?}", result5);
    }
    println!("Test 5 passed: hamming_distance(\"AATG\", \"AAAAA\") = {:?}", result5);

    let result6 = to_option(hamming_distance("G", "TT"));
    if result6 != None {
        panic!("Test 6 failed: Expected None, got {:?}", result6);
    }
    println!("Test 6 passed: hamming_distance(\"G\", \"TT\") = {:?}", result6);

    let result7 = to_option(hamming_distance("GGACGGATTCTG", "AGGACGGATTCTGG"));
    if result7 != None {
        panic!("Test 7 failed: Expected None, got {:?}", result7);
    }
    println!("Test 7 passed: hamming_distance(\"GGACGGATTCTG\", \"AGGACGGATTCTGG\") = {:?}", result7);

    // Test 8: Empty strings (from test_hamming_distance_empty_strings)
    let result8 = to_option(hamming_distance("", ""));
    if result8 != Some(0) {
        panic!("Test 8 failed: Expected Some(0), got {:?}", result8);
    }
    println!("Test 8 passed: hamming_distance(\"\", \"\") = {:?}", result8);

    let result9 = to_option(hamming_distance("", "A"));
    if result9 != None {
        panic!("Test 9 failed: Expected None, got {:?}", result9);
    }
    println!("Test 9 passed: hamming_distance(\"\", \"A\") = {:?}", result9);

    let result10 = to_option(hamming_distance("A", ""));
    if result10 != None {
        panic!("Test 10 failed: Expected None, got {:?}", result10);
    }
    println!("Test 10 passed: hamming_distance(\"A\", \"\") = {:?}", result10);

    // Test 11: Large strings (from test_hamming_distance_large_strings)
    let long_string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    let result11 = to_option(hamming_distance(long_string, long_string));
    if result11 != Some(0) {
        panic!("Test 11 failed: Expected Some(0), got {:?}", result11);
    }
    println!("Test 11 passed: hamming_distance(long_string, long_string) = {:?}", result11);

    let modified_long_string = long_string.replace("A", "B");
    let result12 = to_option(hamming_distance(long_string, &modified_long_string));
    if result12 != Some(1) {
        panic!("Test 12 failed: Expected Some(1), got {:?}", result12);
    }
    println!("Test 12 passed: hamming_distance(long_string, modified_long_string) = {:?}", result12);
}

Solution

Click to Show/Hide Solution
#![allow(unused)]

fn main() {
fn hamming_distance(dna1: &str, dna2: &str) -> Result<usize, &'static str> {
    if dna1.len() != dna2.len() {
        return Err("DNA strands must be of equal length");
    }
    Ok(dna1.chars().zip(dna2.chars()).filter(|(a, b)| a != b).count())
}

}

Challenge 34

Rust Challenge: ISBN-10 Validation

Write a function is_valid_isbn10 that checks if a given string is a valid ISBN-10.

An ISBN-10 is 9 digits (0-9) plus a check character (0-9 or ‘X’ for 10), optionally with hyphens. Validate it using the formula: (d₁ * 10 + d₂ * 9 + d₃ * 8 + d₄ * 7 + d₅ * 6 + d₆ * 5 + d₇ * 4 + d₈ * 3 + d₉ * 2 + d₁₀ * 1) mod 11 == 0

fn is_valid_isbn10(isbn: &str) -> bool {
    // your implementation goes here
    // don't touch the code in the main function below
}

fn main() {
    // Test 1: Valid ISBN with hyphens
    let result1 = is_valid_isbn10("3-598-21508-8");
    if !result1 {
        panic!("Test 1 failed: Expected true, got false");
    }
    println!("Test 1 passed: is_valid_isbn10(\"3-598-21508-8\") = true");

    // Test 2: Valid ISBN with X
    let result2 = is_valid_isbn10("3-598-21507-X");
    if !result2 {
        panic!("Test 2 failed: Expected true, got false");
    }
    println!("Test 2 passed: is_valid_isbn10(\"3-598-21507-X\") = true");
}

Solution

Click to Show/Hide Solution
#![allow(unused)]
fn main() {
fn is_valid_isbn10(isbn: &str) -> bool {
    let cleaned: String = isbn.chars().filter(|c| c.is_alphanumeric()).collect();
    if cleaned.len() != 10 {
        return false;
    }

    let mut sum = 0;
    for (i, c) in cleaned.chars().enumerate() {
        let digit = if c == 'X' && i == 9 {
            10 // Check character 'X' represents 10
        } else if c.is_digit(10) {
            c.to_digit(10).unwrap()
        } else {
            return false; // Invalid character
        };
        sum += digit * (10 - i as u32);
    }

    sum % 11 == 0
}
}

Challenge 35

Rust Challenge: Simple Linked List for Task List

Implement a singly linked list for a to-do application where each task is a number (task ID).

Create the list from a range of numbers and add a method to reverse the list to process tasks in the opposite order.


#[derive(Clone)]
struct Node {
    task_id: i32,
    next: Option<Box<Node>>,
}

struct TaskList {
    head: Option<Box<Node>>,
}

impl TaskList {
    // Initialize an empty task list
    fn new() -> Self {
        unimplemented!("Create a new empty TaskList");
    }

    // Create a task list from a range of task IDs (start..=end)
    fn from_range(start: i32, end: i32) -> Self {
        unimplemented!("Implement creating a singly linked list from a range of numbers");
    }

    // Reverse the task list
    fn reverse(&mut self) {
        unimplemented!("Implement reversing the singly linked list");
    }

    // Convert the task list to a vector for testing
    fn to_vec(&self) -> Vec<i32> {
        unimplemented!("Implement converting the linked list to a vector");
    }
}

// don't touch the code in the main function below
fn main() {
    // Test 1: Create task list from range and check order
    let task_list1 = TaskList::from_range(1, 5);
    let result1 = task_list1.to_vec();
    if result1 != vec![1, 2, 3, 4, 5] {
        panic!("Test 1 failed: Expected [1, 2, 3, 4, 5], got {:?}", result1);
    }
    println!("Test 1 passed: {:?}", result1);

    // Test 2: Reverse the task list
    let mut task_list2 = TaskList::from_range(1, 5);
    task_list2.reverse();
    let result2 = task_list2.to_vec();
    if result2 != vec![5, 4, 3, 2, 1] {
        panic!("Test 2 failed: Expected [5, 4, 3, 2, 1], got {:?}", result2);
    }
    println!("Test 2 passed: {:?}", result2);
}

Solution

Click to Show/Hide Solution
#![allow(unused)]
fn main() {
#[derive(Clone)]
struct Node {
    task_id: i32,
    next: Option<Box<Node>>,
}

struct TaskList {
    head: Option<Box<Node>>,
}

impl TaskList {
    fn new() -> Self {
        TaskList { head: None }
    }

    fn from_range(start: i32, end: i32) -> Self {
        let mut task_list = TaskList::new();
        for id in (start..=end).rev() {
            let new_node = Box::new(Node {
                task_id: id,
                next: task_list.head.take(),
            });
            task_list.head = Some(new_node);
        }
        task_list
    }

    fn reverse(&mut self) {
        let mut prev = None;
        let mut current = self.head.take();
        while let Some(mut node) = current {
            let next = node.next.take();
            node.next = prev;
            prev = Some(node);
            current = next;
        }
        self.head = prev;
    }

    fn to_vec(&self) -> Vec<i32> {
        let mut result = Vec::new();
        let mut current = &self.head;
        while let Some(node) = current {
            result.push(node.task_id);
            current = &node.next;
        }
        result
    }
}
}

Challenge 36

Rust Challenge: Atbash Cipher

Implement the Atbash cipher, a substitution cipher that maps each letter to its reverse in the alphabet (a→z, b→y, …, z→a).

Encode text in lowercase, group letters in blocks of 5 (excluding numbers and punctuation), and decode back to the original text.

fn encode(plain: &str) -> String {
    // your implementation goes here
    // don't touch the code in the main function below
}

fn decode(ciphered: &str) -> String {
    // your implementation goes here
    // don't touch the code in the main function below
}

fn main() {
    // Test 1: Encode "test" -> "gvhg"
    let result1 = encode("test");
    if result1 != "gvhg" {
        panic!("Test 1 failed: Expected 'gvhg', got '{}'", result1);
    }
    println!("Test 1 passed: encode(\"test\") = '{}'", result1);

    // Test 2: Encode "x123 yes" -> "c123b vh"
    let result2 = encode("x123 yes");
    if result2 != "c123b vh" {
        panic!("Test 2 failed: Expected 'c123b vh', got '{}'", result2);
    }
    println!("Test 2 passed: encode(\"x123 yes\") = '{}'", result2);

    // Test 3: Decode "gvhg" -> "test"
    let result3 = decode("gvhg");
    if result3 != "test" {
        panic!("Test 3 failed: Expected 'test', got '{}'", result3);
    }
    println!("Test 3 passed: decode(\"gvhg\") = '{}'", result3);

    // Test 4: Decode "gsvjf rxpyi ldmul cqfnk hlevi gsvoz abwlt"
    let result4 = decode("gsvjf rxpyi ldmul cqfnk hlevi gsvoz abwlt");
    if result4 != "thequickbrownfoxjumpsoverthelazydog" {
        panic!("Test 4 failed: Expected 'thequickbrownfoxjumpsoverthelazydog', got '{}'", result4);
    }
    println!("Test 4 passed: decode(\"gsvjf rxpyi...\") = '{}'", result4);
}

Solution

Click to Show/Hide Solution
#![allow(unused)]

fn main() {
fn encode(plain: &str) -> String {
    let cipher: Vec<char> = "zyxwvutsrqponmlkjihgfedcba".chars().collect();
    let mut result = String::new();
    let mut count = 0;

    for c in plain.to_lowercase().chars() {
        if c.is_alphabetic() {
            let idx = (c as u8 - b'a') as usize;
            result.push(cipher[idx]);
            count += 1;
        } else if c.is_digit(10) {
            result.push(c);
            count += 1;
        }
        if count == 5 && result.chars().last() != Some(' ') {
            result.push(' ');
            count = 0;
        }
    }
    result.trim_end().to_string()
}

fn decode(ciphered: &str) -> String {
    let plain: Vec<char> = "abcdefghijklmnopqrstuvwxyz".chars().collect();
    let cipher: Vec<char> = "zyxwvutsrqponmlkjihgfedcba".chars().collect();

    ciphered.chars().filter(|c| c.is_alphanumeric()).map(|c| {
        if c.is_digit(10) {
            c
        } else {
            let idx = (cipher.iter().position(|&x| x == c).unwrap()) as usize;
            plain[idx]
        }
    }).collect()
}

}

Challenge 37

Rust Challenge: Matrix Transposition

Write a function transpose that takes a 2D vector (matrix) of integers and returns its transpose (rows become columns, columns become rows).

Handle non-square matrices and empty matrices.

fn transpose(matrix: &Vec<Vec<u32>>) -> Result<Vec<Vec<u32>>, String> {
    // your implementation goes here
    // don't touch the code in the main function below
}

fn main() {
    // Test 1: Square matrix 3x3
    let matrix1 = vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]];
    let result1 = transpose(&matrix1);
    let expected1 = vec![vec![1, 4, 7], vec![2, 5, 8], vec![3, 6, 9]];
    match result1 {
        Ok(res) => {
            if res != expected1 {
                panic!("Test 1 failed: Expected {:?}, got {:?}", expected1, res);
            }
            println!("Test 1 passed: {:?}", res);
        }
        Err(e) => panic!("Test 1 failed with error: {}", e),
    }

    // Test 2: Empty matrix - should return an error
    let matrix2: Vec<Vec<u32>> = vec![];
    let result2 = transpose(&matrix2);
    match result2 {
        Ok(res) => panic!("Test 2 failed: Expected an error for empty matrix, got {:?}", res),
        Err(_) => println!("Test 2 passed: transpose returned an error for empty matrix as expected"),
    }
}

Solution

Click to Show/Hide Solution
#![allow(unused)]

fn main() {
fn transpose(matrix: &Vec<Vec<u32>>) -> Result<Vec<Vec<u32>>, String> {
    if matrix.is_empty() {
        return Err("The input matrix cannot be empty".to_string());
    }
    if matrix[0].is_empty() {
        return Ok(vec![]);
    }
    let rows = matrix.len();
    let cols = matrix[0].len();
    let mut result = vec![vec![0; rows]; cols];
    for i in 0..rows {
        for j in 0..matrix[i].len() {
            result[j][i] = matrix[i][j];
        }
    }
    Ok(result)
}
}

Challenge 38

Rust Challenge: Pathfinding in a Grid

Write a function shortest_path using BFS to find the shortest path length from the top-left (0,0) to the bottom-right (n-1,m-1) in a 2D grid.

The grid uses 0 for empty spaces and 1 for walls. Move only up, down, left, or right (no diagonals), and don’t pass through walls.

fn shortest_path(grid: Vec<Vec<i32>>) -> Option<usize> {
    // your implementation goes here
    // don't touch the code in the main function below
}

fn main() {
    // Test 1: Simple 3x3 grid with path
    let grid1 = vec![
        vec![0, 1, 0],
        vec![0, 1, 0],
        vec![0, 0, 0],
    ];
    let result1 = shortest_path(grid1);
    if result1 != Some(4) {
        panic!("Test 1 failed: Expected Some(4), got {:?}", result1);
    }
    println!("Test 1 passed: {:?}", result1);

    // Test 2: 2x2 grid (from test_shortest_path_5)
    let grid2 = vec![vec![0, 0], vec![0, 0]];
    let result2 = shortest_path(grid2);
    if result2 != Some(2) {
        panic!("Test 2 failed: Expected Some(2), got {:?}", result2);
    }
    println!("Test 2 passed: {:?}", result2);

    // Test 3: No path due to walls blocking the way
    let grid3 = vec![
        vec![0, 0, 0],
        vec![1, 1, 1],
        vec![0, 0, 0],
    ];
    let result3 = shortest_path(grid3);
    if result3 != None {
        panic!("Test 3 failed: Expected None, got {:?}", result3);
    }
    println!("Test 3 passed: {:?}", result3);

    // Test 4: Empty grid
    let grid4: Vec<Vec<i32>> = vec![];
    let result4 = shortest_path(grid4);
    if result4 != None {
        panic!("Test 4 failed: Expected None, got {:?}", result4);
    }
    println!("Test 4 passed: {:?}", result4);
}

Solution

Click to Show/Hide Solution
#![allow(unused)]
fn main() {
fn shortest_path(grid: Vec<Vec<i32>>) -> Option<usize> {
    use std::collections::VecDeque;

    if grid.is_empty() || grid[0].is_empty() {
        return None;
    }

    let rows = grid.len();
    let cols = grid[0].len();

    // Check if start or end is a wall
    if grid[0][0] == 1 || grid[rows - 1][cols - 1] == 1 {
        return None;
    }

    let mut queue = VecDeque::new();
    let mut visited = vec![vec![false; cols]; rows];
    let mut distances = vec![vec![usize::MAX; cols]; rows];

    queue.push_back((0, 0));
    visited[0][0] = true;
    distances[0][0] = 0;

    let directions = [(0, 1), (1, 0), (0, -1), (-1, 0)]; // right, down, left, up

    while let Some((r, c)) = queue.pop_front() {
        if r == rows - 1 && c == cols - 1 {
            return Some(distances[r][c]);
        }

        for &(dr, dc) in &directions {
            let new_r = (r as i32 + dr) as usize;
            let new_c = (c as i32 + dc) as usize;

            if new_r < rows && new_c < cols && !visited[new_r][new_c] && grid[new_r][new_c] == 0 {
                queue.push_back((new_r, new_c));
                visited[new_r][new_c] = true;
                distances[new_r][new_c] = distances[r][c] + 1;
            }
        }
    }

    None
}
}

Challenge 39

Rust Challenge: Unique Characters in a String

Write a function has_unique_chars that checks if a string has all unique characters (case-sensitive). Ignore spaces and special characters, focusing only on ASCII alphanumeric characters.


use std::collections::HashSet;

pub fn has_unique_chars(input: &str) -> bool {
    // your implementation goes here
    // don't touch the code in the main function below
}

fn main() {
    // Test 1: Empty string
    let result1 = has_unique_chars("");
    if !result1 {
        panic!("Test 1 failed: Expected true, got false");
    }
    println!("Test 1 passed: has_unique_chars(\"\") = true");

    // Test 2: All unique characters
    let result2 = has_unique_chars("abcdefg");
    if !result2 {
        panic!("Test 2 failed: Expected true, got false");
    }
    println!("Test 2 passed: has_unique_chars(\"abcdefg\") = true");

    // Test 3: String with duplicates
    let result3 = has_unique_chars("aabbcc");
    if result3 {
        panic!("Test 3 failed: Expected false, got true");
    }
    println!("Test 3 passed: has_unique_chars(\"aabbcc\") = false");

    // Test 4: Single character
    let result4 = has_unique_chars("a");
    if !result4 {
        panic!("Test 4 failed: Expected true, got false");
    }
    println!("Test 4 passed: has_unique_chars(\"a\") = true");

    // Test 5: Case sensitivity
    let result5 = has_unique_chars("Aa");
    let result6 = has_unique_chars("AA");
    if !result5 {
        panic!("Test 5a failed: Expected true, got false");
    }
    if result6 {
        panic!("Test 5b failed: Expected false, got true");
    }
    println!("Test 5 passed: has_unique_chars(\"Aa\") = true, has_unique_chars(\"AA\") = false");

    // Test 6: Spaces in string
    let result7 = has_unique_chars("a b c");
    let result8 = has_unique_chars("a b b c");
    if !result7 {
        panic!("Test 6a failed: Expected true, got false");
    }
    if result8 {
        panic!("Test 6b failed: Expected false, got true");
    }
    println!("Test 6 passed: has_unique_chars(\"a b c\") = true, has_unique_chars(\"a b b c\") = false");

    // Test 7: Large string with no duplicates
    let long_str1 = "abcdefghijklmnopqrstuvwxyz";
    let result10 = has_unique_chars(long_str1);
    if !result10 {
        panic!("Test 8 failed: Expected true, got false");
    }
    println!("Test 8 passed: has_unique_chars(\"long unique string\") = true");

    // Test 8: Large string with duplicates
    let long_str2 = "abcabcabc".repeat(100);
    let result11 = has_unique_chars(&long_str2);
    if result11 {
        panic!("Test 9 failed: Expected false, got true");
    }
    println!("Test 9 passed: has_unique_chars(\"large duplicated string\") = false");

    // Test 9: Mixed case with special characters
    let result12 = has_unique_chars("AbC!@#");
    let result13 = has_unique_chars("AbC!@#A");
    if !result12 {
        panic!("Test 10a failed: Expected true, got false");
    }
    if result13 {
        panic!("Test 10b failed: Expected false, got true");
    }
    println!("Test 10 passed: has_unique_chars(\"AbC!@#\") = true, has_unique_chars(\"AbC!@#A\") = false");
}

Solution

Click to Show/Hide Solution
#![allow(unused)]
fn main() {
use std::collections::HashSet;

pub fn has_unique_chars(input: &str) -> bool {
    let mut seen = HashSet::new();

    for ch in input.chars() {
        if ch.is_whitespace() {
            continue;
        }

        if !seen.insert(ch) {
            return false;
        }
    }

    true
}
}

Challenge 40

Rust Challenge: Longest substring without repeating characters

Write a function longest_unique_substring that takes a string as input and returns the longest substring that does not contain any repeated characters.


pub fn longest_unique_substring(s: &str) -> String {
    // your implementation goes here
    // don't touch the code in the main function below
}

fn main() {
    // Test 1: Empty string
    let result1 = longest_unique_substring("");
    if result1 != "" {
        panic!("Test 1 failed: Expected \"\", got {:?}", result1);
    }
    println!("Test 1 passed: longest_unique_substring(\"\") = {:?}", result1);

    // Test 2: All unique characters
    let result2 = longest_unique_substring("abcdefg");
    if result2 != "abcdefg" {
        panic!("Test 2 failed: Expected \"abcdefg\", got {:?}", result2);
    }
    println!("Test 2 passed: longest_unique_substring(\"abcdefg\") = {:?}", result2);

    // Test 3: String with duplicates
    let result3 = longest_unique_substring("aabbcc");
    if result3 != "ab" {
        panic!("Test 3 failed: Expected \"ab\", got {:?}", result3);
    }
    println!("Test 3 passed: longest_unique_substring(\"aabbcc\") = {:?}", result3);

    // Test 4: Single character
    let result4 = longest_unique_substring("a");
    if result4 != "a" {
        panic!("Test 4 failed: Expected \"a\", got {:?}", result4);
    }
    println!("Test 4 passed: longest_unique_substring(\"a\") = {:?}", result4);

    // Test 5: Mixed case
    let result5 = longest_unique_substring("abcABCabc");
    if result5 != "abcABC" {
        panic!("Test 5 failed: Expected \"abcABC\", got {:?}", result5);
    }
    println!("Test 5 passed: longest_unique_substring(\"abcABCabc\") = {:?}", result5);

    // Test 6: Multiple spaces
    let result6 = longest_unique_substring("abc abc abc");
    if result6 != "abc " {
        panic!("Test 6 failed: Expected \"abc \", got {:?}", result6);
    }
    println!("Test 6 passed: longest_unique_substring(\"abc abc abc\") = {:?}", result6);

    // Test 7: Large string with no duplicates
    let long_str1 = "abcdefghijklmnopqrstuvwxyz";
    let result7 = longest_unique_substring(long_str1);
    if result7 != long_str1 {
        panic!("Test 7 failed: Expected \"{}\", got {:?}", long_str1, result7);
    }
    println!("Test 7 passed: longest_unique_substring(\"long unique string\") = {:?}", result7);

    // Test 8: Large string with duplicates
    let long_str2 = "abcabcabc".repeat(100);
    let result8 = longest_unique_substring(&long_str2);
    if result8 != "abc" {
        panic!("Test 8 failed: Expected \"abc\", got {:?}", result8);
    }
    println!("Test 8 passed: longest_unique_substring(\"large duplicated string\") = {:?}", result8);
}

Solution

Click to Show/Hide Solution
#![allow(unused)]
fn main() {
pub fn longest_unique_substring(s: &str) -> String {
    let mut start = 0;
    let mut max_len = 0;
    let mut max_start = 0;
    let mut seen = std::collections::HashMap::new();
    let chars: Vec<char> = s.chars().collect();

    for (end, &c) in chars.iter().enumerate() {
        if let Some(&prev) = seen.get(&c) {
            start = start.max(prev + 1);
        }
        seen.insert(c, end);
        let current_len = end - start + 1;
        if current_len > max_len {
            max_len = current_len;
            max_start = start;
        }
    }

    chars[max_start..max_start + max_len].iter().collect()
}
}

Challenge 41

Rust Challenge: Parsing a CSV File

You are given a CSV file that contains user data, including email addresses along with other information.

Your task is to parse the CSV file and extract the email addresses. After extracting the email addresses, you need to determine how many unique email domains are present in the file.

For example, if an email address is user_name@domain.com, the domain would be domain.com.

#![allow(unused)]
fn main() {
pub fn count_unique_domains() -> Result<usize, Box<dyn Error>>
}

You can download the CSV for this challenge here.

Solution

Click to Show/Hide Solution

You can find the solution for this challenge on GitHub.

Challenge 42

Rust Challenge: Group and Count Consecutive Elements

Given a vector of integers, create a function group_and_count that groups consecutive identical elements together and returns a vector of tuples, where each tuple contains an element and its count in the sequence.


fn group_and_count(nums: Vec<i32>) -> Vec<(i32, usize)> {
    // your implementation goes here
    // don't touch the code in the main function below
}

fn main() {
    // Test 1: Basic case with consecutive elements
    let result1 = group_and_count(vec![1, 1, 2, 2, 2, 3, 3, 1]);
    let expected1 = vec![(1, 2), (2, 3), (3, 2), (1, 1)];
    if result1 != expected1 {
        panic!("Test 1 failed: Expected {:?}, got {:?}", expected1, result1);
    }
    println!("Test 1 passed: group_and_count([1, 1, 2, 2, 2, 3, 3, 1]) = {:?}", result1);

    // Test 2: Single element repeated
    let result2 = group_and_count(vec![5, 5, 5]);
    let expected2 = vec![(5, 3)];
    if result2 != expected2 {
        panic!("Test 2 failed: Expected {:?}, got {:?}", expected2, result2);
    }
    println!("Test 2 passed: group_and_count([5, 5, 5]) = {:?}", result2);

    // Test 3: Empty vector
    let result3 = group_and_count(vec![]);
    let expected3: Vec<(i32, usize)> = vec![];
    if result3 != expected3 {
        panic!("Test 3 failed: Expected {:?}, got {:?}", expected3, result3);
    }
    println!("Test 3 passed: group_and_count([]) = {:?}", result3);

    // Test 4: No consecutive elements
    let result4 = group_and_count(vec![1, 2, 3, 4, 5]);
    let expected4 = vec![(1, 1), (2, 1), (3, 1), (4, 1), (5, 1)];
    if result4 != expected4 {
        panic!("Test 4 failed: Expected {:?}, got {:?}", expected4, result4);
    }
    println!("Test 4 passed: group_and_count([1, 2, 3, 4, 5]) = {:?}", result4);

    // Test 5: All elements the same
    let result5 = group_and_count(vec![2, 2, 2, 2, 2]);
    let expected5 = vec![(2, 5)];
    if result5 != expected5 {
        panic!("Test 5 failed: Expected {:?}, got {:?}", expected5, result5);
    }
    println!("Test 5 passed: group_and_count([2, 2, 2, 2, 2]) = {:?}", result5);
}

Solution

Click to Show/Hide Solution
#![allow(unused)]

fn main() {
fn group_and_count(nums: Vec<i32>) -> Vec<(i32, usize)> {
    let mut result = Vec::new();
    if nums.is_empty() {
        return result;
    }

    let mut current_num = nums[0];
    let mut current_count = 1;

    for &num in nums.iter().skip(1) {
        if num == current_num {
            current_count += 1;
        } else {
            result.push((current_num, current_count));
            current_num = num;
            current_count = 1;
        }
    }
    result.push((current_num, current_count));
    result
}
}

Challenge 43

Palindrome Permutation Check

Given a string s, determine if a permutation of the string can form a palindrome after:

  • Ignoring all non-alphanumeric characters.
  • Treating uppercase and lowercase letters as identical.

Return true if the criteria are satisfied, otherwise false.


pub fn can_form_palindrome(s: &str) -> bool {
    // your implementation goes here
    // don't touch the code in the main function below
}

fn main() {
    // Test 1: "Tact Coa" (can form "taco cat")
    let result1 = can_form_palindrome("Tact Coa");
    if !result1 {
        panic!("Test 1 failed: Expected true, got false");
    }
    println!("Test 1 passed: can_form_palindrome(\"Tact Coa\") = true");

    // Test 2: "hello" (cannot form a palindrome)
    let result2 = can_form_palindrome("hello");
    if result2 {
        panic!("Test 2 failed: Expected false, got true");
    }
    println!("Test 2 passed: can_form_palindrome(\"hello\") = false");

    // Test 3: "A man a plan a canal Panama" (can form a palindrome)
    let result3 = can_form_palindrome("A man a plan a canal Panama");
    if !result3 {
        panic!("Test 3 failed: Expected true, got false");
    }
    println!("Test 3 passed: can_form_palindrome(\"A man a plan a canal Panama\") = true");

    // Test 4: Empty string (can form a palindrome)
    let result4 = can_form_palindrome("");
    if !result4 {
        panic!("Test 4 failed: Expected true, got false");
    }
    println!("Test 4 passed: can_form_palindrome(\"\") = true");

    // Test 5: Single character "a" (can form a palindrome)
    let result5 = can_form_palindrome("a");
    if !result5 {
        panic!("Test 5 failed: Expected true, got false");
    }
    println!("Test 5 passed: can_form_palindrome(\"a\") = true");

    // Test 6: "racecarx" (cannot form a palindrome)
    let result6 = can_form_palindrome("racecarx");
    if result6 {
        panic!("Test 6 failed: Expected false, got true");
    }
    println!("Test 6 passed: can_form_palindrome(\"racecarx\") = false");

    // Test 7: "🎩🚀🚀🎩" (can form a palindrome)
    let result7 = can_form_palindrome("🎩🚀🚀🎩");
    if !result7 {
        panic!("Test 7 failed: Expected true, got false");
    }
    println!("Test 7 passed: can_form_palindrome(\"🎩🚀🚀🎩\") = true");
}

Solution

Click to Show/Hide Solution
#![allow(unused)]

fn main() {
pub fn can_form_palindrome(s: &str) -> bool {
    let mut char_count = std::collections::HashMap::new();

    for c in s.chars().filter(|c| c.is_alphanumeric()) {
        *char_count.entry(c.to_ascii_lowercase()).or_insert(0) += 1;
    }
    
    let odd_count = char_count.values().filter(|&&count| count % 2 != 0).count();

    odd_count <= 1
}

}

Challenge 44

Smallest Window Containing All Characters

Given two strings, s and t, find the smallest substring of s that contains all the characters of t (including duplicates).

  • If no such substring exists, return None.
  • If there are multiple valid substrings, return the first one found.
  • The input strings may contain any printable ASCII characters.

You should aim for O(n) time complexity using an efficient approach.

Example:


pub fn smallest_window(s: &str, t: &str) -> Option<String> {
    // your implementation goes here
    // don't touch the code in the main function below
}

fn main() {
    // Test 1: "ADOBECODEBANC", "ABC" -> "BANC"
    let result1 = smallest_window("ADOBECODEBANC", "ABC");
    let expected1 = Some("BANC".to_string());
    if result1 != expected1 {
        panic!("Test 1 failed: Expected {:?}, got {:?}", expected1, result1);
    }
    println!("Test 1 passed: smallest_window(\"ADOBECODEBANC\", \"ABC\") = {:?}", result1);

    // Test 2: "a", "a" -> "a"
    let result2 = smallest_window("a", "a");
    let expected2 = Some("a".to_string());
    if result2 != expected2 {
        panic!("Test 2 failed: Expected {:?}, got {:?}", expected2, result2);
    }
    println!("Test 2 passed: smallest_window(\"a\", \"a\") = {:?}", result2);

    // Test 3: "a", "b" -> None
    let result3 = smallest_window("a", "b");
    let expected3 = None;
    if result3 != expected3 {
        panic!("Test 3 failed: Expected {:?}, got {:?}", expected3, result3);
    }
    println!("Test 3 passed: smallest_window(\"a\", \"b\") = {:?}", result3);

    // Test 4: "ab", "b" -> "b"
    let result4 = smallest_window("ab", "b");
    let expected4 = Some("b".to_string());
    if result4 != expected4 {
        panic!("Test 4 failed: Expected {:?}, got {:?}", expected4, result4);
    }
    println!("Test 4 passed: smallest_window(\"ab\", \"b\") = {:?}", result4);

    // Test 5: "abcdef", "xyz" -> None
    let result5 = smallest_window("abcdef", "xyz");
    let expected5 = None;
    if result5 != expected5 {
        panic!("Test 5 failed: Expected {:?}, got {:?}", expected5, result5);
    }
    println!("Test 5 passed: smallest_window(\"abcdef\", \"xyz\") = {:?}", result5);
}

Solution

Click to Show/Hide Solution
#![allow(unused)]

fn main() {
pub fn smallest_window(s: &str, t: &str) -> Option<String> {
    if s.is_empty() || t.is_empty() || s.len() < t.len() {
        return None;
    }

    // Count frequencies of characters in t
    let mut t_freq = std::collections::HashMap::new();
    for c in t.chars() {
        *t_freq.entry(c).or_insert(0) += 1;
    }

    let required = t_freq.len();
    let mut formed = 0;
    let mut window_freq = std::collections::HashMap::new();

    let mut min_len = usize::MAX;
    let mut min_window_start = 0;

    let mut left = 0;
    let mut right = 0;
    let chars: Vec<char> = s.chars().collect();

    while right < chars.len() {
        // Add character at right to window
        let c = chars[right];
        *window_freq.entry(c).or_insert(0) += 1;

        // Check if this character fulfills a requirement
        if t_freq.contains_key(&c) && window_freq[&c] == t_freq[&c] {
            formed += 1;
        }

        // Shrink the window from the left
        while left <= right && formed == required {
            let c = chars[left];
            let window_len = right - left + 1;
            if window_len < min_len {
                min_len = window_len;
                min_window_start = left;
            }

            *window_freq.get_mut(&c).unwrap() -= 1;
            if t_freq.contains_key(&c) && window_freq[&c] < t_freq[&c] {
                formed -= 1;
            }
            left += 1;
        }
        right += 1;
    }

    if min_len == usize::MAX {
        None
    } else {
        Some(chars[min_window_start..min_window_start + min_len].iter().collect())
    }
}
}

Challenge 45

Longest Increasing Subsequence

You are given an array of integers nums, find the length of the longest increasing subsequence. A subsequence is a sequence derived from the array by deleting some or no elements without changing the order of the remaining elements.

Example:


pub fn longest_increasing_subsequence(nums: Vec<i32>) -> usize {
    // your implementation goes here
    // don't touch the code in the main function below
}

fn main() {
    // Test 1: Standard case [10, 9, 2, 5, 3, 7, 101, 18] -> 4 (e.g., [2, 3, 7, 101])
    let result1 = longest_increasing_subsequence(vec![10, 9, 2, 5, 3, 7, 101, 18]);
    let expected1 = 4;
    if result1 != expected1 {
        panic!("Test 1 failed: Expected {}, got {}", expected1, result1);
    }
    println!("Test 1 passed: longest_increasing_subsequence([10, 9, 2, 5, 3, 7, 101, 18]) = {}", result1);

    // Test 2: Repeated elements [7, 7, 7, 7, 7, 7] -> 1
    let result2 = longest_increasing_subsequence(vec![7, 7, 7, 7, 7, 7]);
    let expected2 = 1;
    if result2 != expected2 {
        panic!("Test 2 failed: Expected {}, got {}", expected2, result2);
    }
    println!("Test 2 passed: longest_increasing_subsequence([7, 7, 7, 7, 7, 7]) = {}", result2);

    // Test 3: Strictly increasing [1, 2, 3, 4, 5, 6] -> 6
    let result3 = longest_increasing_subsequence(vec![1, 2, 3, 4, 5, 6]);
    let expected3 = 6;
    if result3 != expected3 {
        panic!("Test 3 failed: Expected {}, got {}", expected3, result3);
    }
    println!("Test 3 passed: longest_increasing_subsequence([1, 2, 3, 4, 5, 6]) = {}", result3);

    // Test 4: Strictly decreasing [5, 4, 3, 2, 1] -> 1
    let result4 = longest_increasing_subsequence(vec![5, 4, 3, 2, 1]);
    let expected4 = 1;
    if result4 != expected4 {
        panic!("Test 4 failed: Expected {}, got {}", expected4, result4);
    }
    println!("Test 4 passed: longest_increasing_subsequence([5, 4, 3, 2, 1]) = {}", result4);

    // Test 5: Mixed sequence [1, 3, 5, 4, 7] -> 4 (e.g., [1, 3, 4, 7])
    let result5 = longest_increasing_subsequence(vec![1, 3, 5, 4, 7]);
    let expected5 = 4;
    if result5 != expected5 {
        panic!("Test 5 failed: Expected {}, got {}", expected5, result5);
    }
    println!("Test 5 passed: longest_increasing_subsequence([1, 3, 5, 4, 7]) = {}", result5);

    // Test 6: Repeated with subsequences [0, 1, 0, 3, 2, 3] -> 4 (e.g., [0, 1, 2, 3])
    let result6 = longest_increasing_subsequence(vec![0, 1, 0, 3, 2, 3]);
    let expected6 = 4;
    if result6 != expected6 {
        panic!("Test 6 failed: Expected {}, got {}", expected6, result6);
    }
    println!("Test 6 passed: longest_increasing_subsequence([0, 1, 0, 3, 2, 3]) = {}", result6);

    // Test 7: Single element [5] -> 1
    let result7 = longest_increasing_subsequence(vec![5]);
    let expected7 = 1;
    if result7 != expected7 {
        panic!("Test 7 failed: Expected {}, got {}", expected7, result7);
    }
    println!("Test 7 passed: longest_increasing_subsequence([5]) = {}", result7);
}

Solution

Click to Show/Hide Solution
#![allow(unused)]

fn main() {
pub fn longest_increasing_subsequence(nums: Vec<i32>) -> usize {
    if nums.is_empty() {
        return 0;
    }

    let mut dp = vec![1; nums.len()];
    let mut max_len = 1;

    for i in 1..nums.len() {
        for j in 0..i {
            if nums[i] > nums[j] {
                dp[i] = dp[i].max(dp[j] + 1);
            }
        }
        max_len = max_len.max(dp[i]);
    }

    max_len
}
}

Resources

You’ve wrestled with ownership, conquered lifetimes, and tamed the borrow checker.

Rust isn’t just a language—it’s a mindset. Now go build something amazing!

What’s Next?

Put these skills to work on a project, and let me know how it goes—I’d love to hear from you on X @rustaceans_rs.

Tools

Community