Chapter 8 Exercise (Median and Mode) - First time using Rust

Hello everyone,

This is my first time writing Rust. I have been studying from the Official Rust Book without using any AI assistance. I spent almost 3 hours working on this specific exercise (Chapter 8: calculating median and mode) to get it to this point.

Even though it works, I am not sure if there is anything I am missing or if this is the idiomatic way to write Rust.

My questions are:

  1. Are there better or more idiomatic ways to implement this logic?
  2. Are there any edge cases or best practices that I overlooked?
use std::collections::HashMap;

fn main() {

    let mut list_integer = vec![7, 24, 3, 24, 10];

    let mut mode: HashMap<i32, i32> = HashMap::new();
    let _sorted_list = &list_integer.sort_by(|a, b| a.cmp(b));

    for &item in &list_integer{
        mode.entry(item).and_modify(|counter| *counter += 1).or_insert(1);
    }
    
    let find_mode = mode.iter().max_by(|x, y| x.cmp(y)).unwrap();
    let mode_again = find_mode.0;
    let median_cal = &list_integer.iter().enumerate().len() / 2;
    let median = &list_integer[median_cal];

    println!("mode is : {mode_again}");
    println!("median is : {median}");
}

I think the algorithm is mostly ok, just some issues I want to point out.

this line:

I would usually choose a plural form for the name of a variable of a collection type. in this case, I would probably name it counts or freqs.

in this line:

first of all, the sorting method doesn't return a value (or you can say it returns the unit type).

more importantly, you don't need sort_by() in this case, just sort() will do.

for the "mode" in these lines:

the iter() method yields key-value pairs, and pairs are compared lexically, I don't think this will find the correct "mode"?

if my statistics classes still serves me, I recall the "mode" is the most frequent data. so you should be compare the frequency instead of the pair.

this is how I would do it:

let mode = freqs.iter().max_by_key(|(_, freq)| freq).map(|(data, _)| data).unwrap();

finally, the median position in this line:

don't use an iterator to calculate the len of an array/slice/Vec, instead, call the len() method on the collection directly.

Thank you so much for taking the time to review my code and for explaining each point so clearly!

I didn't realize tuples are compared lexically, so that explains why my mode only worked by coincidence with my test data.

I also wanted to explain why my code ended up so roundabout. I was reading through the standard library API docs and trying to find the right methods, but sometimes I just couldn't find what I needed. A lot of the odd choices (like iter().enumerate().len()) came from me scrolling through the autocomplete list, reading what each method does, and checking the examples to see if it could fit.

Do you have any tips on how to navigate the docs more effectively, or how to know which methods exist for a type? Any advice for a beginner would be much appreciated.

it all comes down to knowing what operations are meaningful for a data structure and how familiar you are with the library. many of the standard libraries conventions and idioms become part of your "vocabulary" as you get used to them. for example, when you see a new api for the first time, even if you don't know what exactly it does, if the name matches certain patterns, such as as_xxx(), to_xxx(), into_xxx(), from_xxx(), with_xxx(), you can probably guess with high confidence.

slices are one of the most used types. you will likely use arrays and Vecs through the slice apis, since they Deref to slices.

you can think of a slice is just a non-owning array. what's a slice? it's just a pointer and a length. so the most basic apis are as_ptr() and len().

on top of that, any operation requiring random access can be derived, the most basic form is indexing.

so for many "standard" algorithms, generally speaking, when it specifically requires a random access container, in rust it is probably defined as a method of the slice type, examples includes sort(), binary_search(), shift_left/right(), rotate_left/right(), etc.

on the other hand, iterators in rust are typically for linear complexity things, so operations like find(), position(), nth(), min(), max(), sum(), these are on iterators, not on slices (or arrays, Vecs).

another aspect of the iterator is that it is the basis of higher order abstraction, many functional programming features can be found on the iterator, such as map(), filter(), take(), zip(), etc.

Just for clarity, “slice” can refer to either [T] or &[T] (and perhaps also &mut [T]). The ambiguity / context-dependent meaning is somewhat unfortunate for teaching new users of the language, but this ends up happening because [T] is very, very commonly used behind a reference, so it’d be a hassle to always say the pedantically precise “slice reference” instead of “slice” when we mean &[T].

A [T] itself is like an array with some fixed but unknown length, like [T; ??] instead of [T; N] (while &[T], a reference to a slice, is the kind of “slice” which is a pointer and a length).