Solutions to the exercises in the official Rust book [Chapter 8]

Hello,

I arrived at Chapter 8 of the official Rust book and at the end there are some exercises. Is there any official (or at least good and well documented) solution out there ? I've tried searching the internets with no success, so I'm asking here!

I'd like to compare proper solutions to my crappy beginner solution to learn more.

Thanks in advance.

There aren’t any official answers to the exercises that I know of, you can post them on this forum and request feedback for it, it’s what I did :slight_smile:

That's correct! Maybe after the book is finished, I can put up solutions :slight_smile:

2 Likes

Ok so here we go. The exercise I did so far :

Given a list of integers, use a vector and return the mean (average), median (when sorted, the value in the middle position), and mode (the value that occurs most often; a hash map will be helpful here) of the list.

I'm open to all sorts of comment :

  • whether there's a better way,
  • whether there's a faster way,
  • memory/ownership management
  • code safety
  • whether it's idiomatic or not
  • code style/formatting
  • etc.

Please remember that I haven't finished the book yet, I'm stil at chapter 8. So I refrained myself from using features explained after like lambdas, function level comment/doc-string and all the things I don't even know yet.

Also, while I'm "happy" with my solutions for the mean and median (they handle empty vectors and NaN somehow), the code for mode is.... beginner-level crap haha.

Thanks in advance for all your comments!

fn exercise1() {
    let int_list: Vec<i32> = vec![1, 2, 3, 3, 4, 5, 5, 5, 6, 7, 51];

    match mean(&int_list) {
        Some(avg) => println!("mean {}", avg),
        None => println!("mean unavailable for {:?}", int_list),
    }
    match median((&int_list)) {
        Some(result) => { println!("median {}", result)},
        None => { println!("median unavailable for {:?}", int_list);}
    }

    println!("mode {}", mode(&int_list));
}

fn mean(int_list: &Vec<i32>) -> Option<f64> {
    let sum: i32 = int_list.iter().sum();
    let maybe_mean = sum as f64 / int_list.len() as f64;

    if maybe_mean.is_nan() {
        None
    } else {
        Some(maybe_mean)
    }
}

fn median(int_list: &Vec<i32>) -> Option<i32> {
    let mut new_list = int_list.clone();
    new_list.sort();

    match (new_list.first(), new_list.last()) {
        (Some(first), Some(last)) => Some((first + last) / 2),
        (_, _) => None,
    }
}

fn mode(int_list: &Vec<i32>) -> i32 {
    let mut occurrences = HashMap::new();

    for v in int_list.iter() {
        let mut count = occurrences.entry(v).or_insert(0);
        *count += 1;
    }

    let mut maximum = occurrences.get(&int_list[0]).expect("No value in occurrences for first value of int_list");
    let mut result = &int_list[0];

    for (key, value) in &occurrences {
        if value > maximum {
            result = *key;
            maximum = value;
        }
    }

    *result
}
``
1 Like

Thanks for sharing this. Your solution to getting the average value looks a lot nicer than mine. However, your "median" function seems to be wrong.

"..return the median( when sorted, the value in the middle position)"

given a sequence of [10,2,45,32,14,38,17,22,93] this, when sorted, leads to:

Value at 0 is : 2
Value at 1 is : 10
Value at 2 is : 14
Value at 3 is : 17
Value at 4 is : 22 <- median
Value at 5 is : 32
Value at 6 is : 38
Value at 7 is : 45
Value at 8 is : 93

your result is 47.
I am too ashamed to post my code- honestly its a disgrace :smile:

The median function takes the first and last value of the sorted vector ((2+93)/2=47.5) and not the middle value.

You might want to run clippy and check out the result and explanations of those warnings.

An other solution to the mode problem is to take the sorted vector as input. You can than count occurrences as they are all grouped.

It shouldn't, median takes the value from the middle of the ordered collection. If the collection has an even count of elements, the "middle" value is interpolated by calculating the average of the two values closest to the center.

1 Like

Correct, bad wording of mine. What I meant was that Djebbzz his implementation uses the first and last value of the sorted list. And he should use the middle values.

There are a number of issues with the code above:
a) i think its nicer to check if the list holds any elements rathen than running in to a division by 0 and checking for nan (not a number)
b) the median is calculated incorrectly as i pointed out, also median should be returned as a float to be correct in all cases.
c) mode can be undefined, the code does not handle this

Here is my (updated: complete) solution for exercise 8.1. I am thankful for any ideas on how to improve this!

use std::collections::HashMap;

pub fn calc_avg( l: &Vec<i32> ) -> Option<f64> {
    let sum: i32 = l.iter().sum();
    let len: f64 = l.len() as f64;
    if l.len() > 0 {
        return Some(sum as f64 /len);
    }
    else {
        return None
    }
}

pub fn calc_median( x: &Vec<i32> ) -> Option<f64> {
    let mut l = x.clone();
    l.sort();

    let middle_id = l.len()/2;
    match l.get(middle_id) {
        Some(i) => {
            let mut median = *i as f64;
            if l.len() % 2 == 0 {
                // list of even size 
                median += l[middle_id-1] as f64;
                median = median / 2.0;
            }
            return Some(median);
        }
        _ => return None
    }
}

pub fn calc_mode( x: &Vec<i32>  ) -> Option<i32> {
    // add keys to a hashmap and keep counts as values
    let mut m = HashMap::new();
    for i in x {
        let count = m.entry(i).or_insert(0);
        *count += 1;
    }

    let mut highest_count = 0;
    let mut highkey = 0;
    let mut valid = true;

    for (key,value) in m {
        if value > highest_count {
            highest_count = value;
            highkey = *key;
            valid = true;
        }
        else if value == highest_count {
            valid = false;
        }
    }

    if highest_count >= 0 && valid == true {
        Some(highkey)
    } 
    else {
        None
    }    
}

pub fn main() {
    let v: Vec<i32> = vec![4,1,5,3,2,16,5,5,1,1];

    match calc_avg(&v) {
        Some(result) => { println!("average is {}", result)},
        None => { println!("average undefined for {:?}", v);}
    }
    match calc_median(&v) {
        Some(result) => { println!("median is {}", result)},
        None => { println!("median undefined for {:?}", v);}
    }
    match calc_mode( &v ) {
        Some(value) => { println!("mode is {}",value) },
        None => { println!("mode undefined for {:?}", v);}
    }
}
1 Like

What would be nicer is if it were more interactive, like Codecademy and Khan Academy.

Take a look at how it explains how to do programming
https://www.khanacademy.org/computing/computer-programming/programming/drawing-basics/pt/drawing-more-shapes-with-code

In this link, it is an interactive video, meaning that you can change code inside the video so you can experiment.

https://www.khanacademy.org/computing/computer-programming/programming/drawing-basics/pc/challenge-simple-snowman
In this link I like how they give you an interactive exercise, and I wish to see that in Rust's documentation as well.

Unfortunately, the technology to make paper interactive has not been perfected yet :wink: (The book is printed by No Starch Press, and first and foremost is written for the purpose of being printed).

If you're looking for more interactivity, I would suggest checking out other resources such as rustlings or the Rust track in exercism.

4 Likes

Lol XDD I thought you guys were talking about the Rust book online.

Sure I will check em out thanks for hte suggestion :slight_smile:

Thank you everyone!

I gave up learning Rust a long time ago, but I recently re-started, using the "Programming Rust" book instead. So far so good!

I'll be sure to check again all your suggestions once I finish the book. See you in some far future :wink:

1 Like

I started learning 3 days ago.
Here is my humble contribution:

use rand::Rng;
use std::collections::HashMap;

fn main() {
    let mut rng = rand::thread_rng();
    let mut numbers: Vec<u32> = (0..20).map(|_| {
        rng.gen_range(0, 6)
    }).collect();
    numbers.sort();

    let sum: u32 = numbers.iter().sum();

    let mean = sum as f64 / numbers.len() as f64;

    let median = numbers[numbers.len() / 2];

    let mut occ: HashMap<u32, u32> = HashMap::new();
    let mut mode : (u32, u32) = (0, 0);
    for (i, v) in numbers.iter().enumerate() {
        // occ.insert(i as u32, *v);
        let count = occ.entry(*v).or_insert(0);
        *count += 1;

        if *count > mode .1 {
            mode  = (*v, *count);
        }
    }

    println!("numbers {:?}", numbers);
    println!("sum {}", sum);
    println!("mean {}", mean);
    println!("median {}", median);
    println!("most frequent is {} with {} occurrences", mode .0, mode .1);
}

Any comments are welcome :slight_smile:

1 Like

A handful of YMMV options:

There are some new things in std::iter that give additional options here, such as

let mut numbers: Vec<u32> = std::iter::repeat_with(|| rng.gen_range(0, 6)).take(20).collect();

You could use .cloned() (or, in 1.36, .copied()) here to remove the need for *ing v later.

One could also consider using max_by_key on occ after the loop to find the mode.

Not sure if this thread is still active, but I found it while doing the exercises in chapter 8 and wanted to compare my solutions to how other people have solved them, just like the thread starter.

Feedback is appreciated!

calculate median, mode and average:

use std::collections::HashMap;

fn main() {
    let numbers = vec![10, 12, 24, 36, 42, 35, 46, 60, 19, 10, 12];
    let avg = calculate_average(&numbers);
    let median = calculate_median(&numbers);
    let mode = calculate_mode(&numbers);

    println!("median: {}", median);
    println!("average: {}", avg);
    println!("mode: {}", mode);
}


fn calculate_median(numbers: &Vec<i32>) -> f32 {
    let mut sorted = numbers.clone();
    sorted.sort();

    let mid = sorted.len() / 2;
    if sorted.len() % 2 == 0 {
        let x = sorted[mid] as f32;
        let y = sorted[mid - 1] as f32;
        return (x + y) * 0.5;
    }
    sorted[mid] as f32
}

fn calculate_mode(numbers: &Vec<i32>) -> i32 {
    let mut times = HashMap::new();
    for x in numbers {
        let count = times.entry(x).or_insert(0);
        *count += 1;
    }

    let mut best_key = numbers[0];
    let mut best_val = times.get(&best_key).unwrap();

    for (key, value) in &times {
        if value > best_val {
            best_key = **key;
            best_val = value;
        }
    }
    best_key
}

fn calculate_average(numbers: &Vec<i32>) -> f32 {
    let mut sum = 0;
    for x in numbers {
        sum += x;
    }

    sum as f32 / numbers.len() as f32
    // numbers.iter().sum::<i32>() as f32 / numbers.len() as f32
}

piglatin

use std::collections::HashMap;

fn main() {
    let t1 = String::from("first");
    let p1 = to_piglatin(&t1);
    assert_eq!(String::from("irst-fay"), p1);
    let t2 = String::from("apple");
    let p2 = to_piglatin(&t2);
    assert_eq!(String::from("apple-hay"), p2);
    let t3 = String::from("first apple");
    let p3 = to_piglatin(&t3);
    assert_eq!(String::from("irst-fay apple-hay"), p3);
}

fn to_piglatin(text: &String) -> String {
    let vowels = vec!['a', 'o', 'u', 'e', 'i', 'y'];

    let words = text.split_whitespace();
    let mut output = String::new();
    for (i, word) in words.enumerate() {
        let first_char = word.chars().next().unwrap();
        if vowels.contains(&first_char) {
            let pig_word = format!("{}-{}", word, "hay");
            if i == 0 {
                output = pig_word;
            } else {
                output = format!("{} {}", output, pig_word);
            }
        } else {
            let pig_word = format!("{}-{}", &word[1..], format!("{}{}", &word[..1], "ay"));
            if i == 0 {
                output = pig_word;
            } else {
                output = format!("{} {}", output, pig_word);
            }
        }
    }
    output
}

Employee list

use std::collections::HashMap;

fn main() {
    {
        let mut company: HashMap<String, Vec<String>> = HashMap::new();
        // handle_command(&command, &company);
        let (name, dep) = get_name_and_department("Add Sally to Engineering".to_string());
        company.entry(dep).or_insert(Vec::new()).push(name);

        let (name, dep) = get_name_and_department("Add Amir to Sales".to_string());
        company.entry(dep).or_insert(Vec::new()).push(name);

        let (name, dep) = get_name_and_department("Add Beate to Sales".to_string());
        company.entry(dep).or_insert(Vec::new()).push(name);

        let (name, dep) = get_name_and_department("Add Cecil to Marketing".to_string());
        company.entry(dep).or_insert(Vec::new()).push(name);

        let sorted = get_all_employees(&company);
        println!("SORTED: {:?}", sorted);

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

fn get_all_employees(company: &HashMap<String, Vec<String>>) -> Vec<String> {
    let mut employees: Vec<String> = Vec::new();

    for (_, value) in company {
        employees = [employees, value.clone()].concat();
    }

    employees.sort();
    employees
}

fn get_name_and_department(s: String) -> (String, String) {
    let words = text_to_words(&s);
    return (String::from(words[1]), String::from(words[words.len() - 1]));
}

fn text_to_words(s: &String) -> Vec<&str> {
    let bytes = s.as_bytes();
    let num_bytes = bytes.len();
    let mut words = Vec::new();
    let mut last_whitespace = 0;

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            words.push(&s[last_whitespace..i]);
            last_whitespace = i + 1;
        } else if i == num_bytes - 1 {
            words.push(&s[last_whitespace..]);
        }
    }
    return words;
}

Wanted to do a fn that takes the company hashmap and adds the employee to the deparment based on the command provided, but did not manage due to some ownership issues...

This is what the handle_command fn looked like

fn main () {
    let mut company: HashMap<String, Vec<String>> = HashMap::new();
    handle_command("Add Sally to Engineering".to_string(), &company);
    println!("{:?}", company);
}
fn handle_command(command: String, company: &mut HashMap<String, Vec<String>>) {
    let (name, dep) = get_name_and_department(command);
    company.entry(dep).or_insert(Vec::new()).push(name);
}

and the error:

mismatched types

types differ in mutability

note: expected mutable reference `&mut std::collections::HashMap<std::string::String, std::vec::Vec<std::string::String>>`
                 found reference `&std::collections::HashMap<std::string::String, std::vec::Vec<std::string::String>>`rustc(E0308)
main.rs(145, 64): types differ in mutability

Please open a new thread instead of reviving ancient ones, but of course the immediate problem there is that you create an immutable reference (&company) whereas the function expects a mutable one (&mut company).

2 Likes

thanks for the reply!

do you have a suggestion for how I should edit the code to make it work? It feels like I tried every possible combination of reference and mut etc to try to make the code compile

You only have to literally change what I wrote above (ie. insert mut after the ampersand).

not really sure where, i have already written &mut as you can see