I've recently finished Chapter 8 of The Book and have started doing the exercises at the end of the chapter. This post pertains to the first exercise which involves writing a program to output the averages of a list of integers. I thought the instructions for this particular exercise was a bit vague, so I tried to make it simple by not including REPL functionality in my implementation. Anyway, here's my current solution:
// Given a list of integers, use a vector and return the mean (the average
// value), 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.
use std::collections::HashMap;
struct Averages {
mean: f64,
median: f64,
mode: Vec<i32>
}
fn sum(vector: &Vec<i32>) -> Option<i32> {
match vector.len() {
0 => None,
_ => {
let mut accumulator = 0;
for element in vector {
accumulator += element;
}
Some(accumulator)
}
}
}
fn mean(vector: &Vec<i32>) -> Option<f64> {
match vector.len() {
0 => None,
length =>
Some((sum(vector).unwrap() as f64) / (length as f64))
}
}
fn is_even(number: &usize) -> bool {
number % 2 == 0
}
fn median(vector_original: &Vec<i32>) -> Option<f64> {
match vector_original.len() {
0 => None,
length => {
let mut vector_cloned = vector_original.to_vec();
vector_cloned.sort_unstable();
match is_even(&length) {
true => {
let length_halved = length / 2;
Some(((
&vector_cloned[length_halved - 1] +
&vector_cloned[length_halved]) as f64) / 2.0)
},
false =>
Some(vector_cloned[((
(length as f64) /
2.0).floor() as usize)] as f64)
}
}
}
}
fn mode(vector: &Vec<i32>) -> Option<Vec<i32>> {
match vector.len() {
0 => None,
_ => {
let mut counts = HashMap::new();
for number in vector {
let count = counts.entry(number).or_insert(1u32);
*count += 1u32;
}
let mut max_count = 0;
for count in counts.values() {
if count > &max_count {
max_count = *count;
}
}
let mut modes: Vec<i32> = Vec::new();
for (number, count) in &counts {
if count == &max_count {
modes.push(**number);
}
}
Some(modes)
}
}
}
fn get_averages(vector: &Vec<i32>) -> Option<Averages> {
match vector.len() {
0 => None,
_ => Some(
Averages {
mean: mean(vector).unwrap(),
median: median(vector).unwrap(),
mode: mode(vector).unwrap()
}
)
}
}
fn print_averages(vector: &Vec<i32>) {
match vector.len() {
0 => println!("Vector is empty."),
_ => {
let averages: Averages = get_averages(vector).unwrap();
println!(
"Mean: {}
Median: {}
Mode(s): {:?}",
&averages.mean,
&averages.median,
&averages.mode
);
}
}
}
fn main() {
print_averages(&(10..=20).collect());
print_averages(&vec![5,5,5,4,3,2,1,1,1]);
print_averages(&vec![]);
}
I wrote the current implementation like I did as I was trying to write it such that each of the functions are reusable independent of each other and in contexts possibly outside of this particular program (hence the arguably redundant calls to len
). But now I'm wondering if using so many unwrap
s like I did is the idiomatic way to handle the Option
s or if there's a better way to have written this instead. I'm especially worried that my implementation might be brittle because I encountered a couple of panics when I was writing and testing it.
I'm also unsure if I'm doing borrowing and moving, as well as referencing and dereferencing optimally, especially in the mode
function; despite it working properly, I'm a little confused
as to what's its actually doing with the data and references, so some explanation about it would be really welcome.
Any other constructive criticisms in general are also welcome, so please nitpick to your hearts content!