Continuing the discussion from Solutions to the exercises in the official Rust book [Chapter 8]:
Here are my implementations of the three exercises in Chapter 8.
Question 1:
use std::io;
use std::collections::HashMap;
fn main() {
let mut numbers = Vec::with_capacity(10);
get_numbers(&mut numbers);
if numbers.len() > 0 {
println!("The average is: {}", get_avg(&numbers));
numbers.sort();
println!("The median is: {}", get_median(&numbers));
match get_mode(&numbers) {
Some(mut v) => if v.len() > 1 {
v.sort();
println!("The modes are: {:?}", v);
} else {
println!("The mode is: {}", &v[0]);
},
None => println!("There is no mode -- each number occurs exactly once!"),
}
} else {
println!("No numbers entered!");
}
}
fn get_numbers(numbers: &mut Vec<i32>) {
println!("Enter the numbers and * to exit:");
loop {
let mut num = String::new();
io::stdin().read_line(&mut num)
.expect("Failed to read line");
if num.trim() == "*" {
break;
}
let num = match num.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
numbers.push(num);
}
}
fn get_avg(numbers: &Vec<i32>) -> f64 {
let mut result = 0;
for number in numbers {
result += number;
}
result as f64 / numbers.len() as f64
}
fn get_median(numbers: &Vec<i32>) -> f64 {
let even = numbers.len() % 2 == 0;
let mut middle = numbers.len() / 2;
if even {
(numbers[middle-1] + numbers[middle]) as f64 / 2.0
} else {
numbers[middle] as f64
}
}
fn get_mode(numbers: &Vec<i32>) -> Option<Vec<i32>> {
let mut map = HashMap::new();
for num in numbers {
let count = map.entry(num).or_insert(0);
*count += 1;
}
let mut largest_val = 1;
for (_, value) in &map {
if *value > largest_val && *value > 1 {
largest_val = *value;
}
}
let mut modes = Vec::with_capacity(2);
let mut mode = None;
if largest_val > 1 {
for (key, value) in map {
if value == largest_val {
modes.push(*key);
}
}
mode = Some(modes);
}
mode
}
Question 2:
use std::io;
fn main() {
println!("Enter a sentence:");
let mut s = String::new();
io::stdin().read_line(&mut s)
.expect("failed to read line");
let pig_latin = pig_latin(s);
println!("{}", pig_latin);
}
fn pig_latin(mut s: String) -> String {
let mut res = String::new();
s = s.to_lowercase();
for word in s.split_whitespace() {
if let Some(c) = word.chars().next() {
if c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u' {
res.push_str(&format!("{}-hay ", word));
} else {
res.push_str(&format!("{}-{}ay ", &word[1..], &c));
}
} else {
println!("No string given");
}
}
res
}
Question 3:
use std::io;
use std::collections::HashMap;
fn main() {
println!("Type \"Add <name> to <department>\" to add a person");
println!("Type \"Retrieve <department>\" or \"Retrieve all\"");
println!("to get a sorted list of names by department");
println!("Type \"Exit\" to exit");
let mut depts: HashMap<String, Vec<String>> = HashMap::new();
loop {
let command = get_cmd();
match parse_cmd(command) {
Cmd::Add(id) => add_name(id, &mut depts),
Cmd::Ret(dept) => {
if let "all" = &dept[..] {
retrieve_all(&mut depts);
} else {
retrieve_dept(dept, &mut depts);
}
},
Cmd::Exit => break,
Cmd::Error(e) => println!("Error parsing command: {}", e),
}
}
}
fn get_cmd() -> String {
println!("\nWhat do you want to do?");
let mut cmd = String::new();
io::stdin().read_line(&mut cmd)
.expect("Failed to read line");
cmd
}
struct CompanyID {
name: String,
dept: String,
}
impl CompanyID {
fn new() -> CompanyID {
CompanyID { name: String::from(""), dept: String::from(""), }
}
}
enum Cmd {
Add(CompanyID),
Ret(String),
Exit,
Error(&'static str),
}
fn parse_cmd(cmd: String) -> Cmd {
let mut words = cmd.split_whitespace();
match words.next() {
Some("Add") => {
if let Some(x) = words.next() {
let mut before_to = true;
let mut id = CompanyID::new();
id.name.push_str(x);
loop {
if let Some(x) = words.next() {
if x == "to" {
before_to = false;
continue;
}
if before_to {
id.name.push_str(&(" ".to_owned() + x));
} else {
if id.dept != "" {
id.dept.push_str(" ");
}
id.dept.push_str(x);
}
} else {
break;
}
}
if id.dept == "" {
Cmd::Error("No department given to write to")
} else {
Cmd::Add(id)
}
} else {
Cmd::Error("No name given")
}
},
Some("Retrieve") => {
if let Some(x) = words.next() {
if x == "all" {
Cmd::Ret(x.to_string())
} else {
Cmd::Ret(cmd[9..].trim().to_string())
}
} else {
Cmd::Error("No department given to read from")
}
},
Some("Exit") => Cmd::Exit,
_ => Cmd::Error("Invalid input"),
}
}
fn add_name(id: CompanyID, depts: &mut HashMap<String, Vec<String>>) {
let dept = &id.dept;
if depts.contains_key(dept) {
// unwrap() is safe here, because we know the value exists.
let names = depts.get_mut(dept).unwrap();
names.push(id.name);
names.sort();
} else {
depts.insert(id.dept, vec![id.name]);
}
}
fn retrieve_all(depts: &HashMap<String, Vec<String>>) {
for key in depts.keys() {
retrieve_dept(key.to_string(), depts);
}
}
fn retrieve_dept(dept: String, depts: &HashMap<String, Vec<String>>) {
if let Some(names) = depts.get(&dept) {
println!("{}", &dept);
for name in names {
println!(" {}", name);
}
} else {
println!("No {} department", dept);
}
}
I don't know how idiomatic it is, but it does the job Also remember that I just read the Book up till chapter 8 and programmed basic C++ and Delphi/Pascal before, so I am not too worried about the fact that I didn't use iterators. I will discover them as I get more proficient with Rust. That said, any comments are welcome!