I recently finished chapter 8 of Rust's book, and below is my solution to the third exercise:
Using a hash map and vectors, create a text interface to allow a user to add employee names to a department in a company. For example, “Add Sally to Engineering” or “Add Amir to Sales.” Then let the user retrieve a list of all people in a department or all people in the company by department, sorted alphabetically.
I'd appreciate pointers on how the code can be improved. Thanks in advance.
use std::io;
use std::collections::HashMap;
//An attempt at Rust book's chapter 8's third exercise:
//https://doc.rust-lang.org/book/ch08-03-hash-maps.html
fn main() {
println!("The Office - Text Interface.");
println!();
println!("Enter a query, type HELP for a list of keyword and their functions, or type EXIT to exit.");
println!();
//build hashmap{department: vec[names]} database, insert default values
let mut company = HashMap::new();
let depts = vec!["SALES", "ENGINEERING", "HR", "SANITATION"];
let sales = vec!["Sally", "Jordan", "Charlie", "Abigail"];
let engineering = vec!["Suzy", "Jay", "Chi", "Amy"];
let hr = vec!["Son", "Jack", "Chia", "Anna"];
let sanitation = vec!["August", "Entangle", "Will", "Jada"];
let tup = [sales, engineering, hr, sanitation];
let mut g: Vec<_> = Vec::new();
company = depts.into_iter()
.map(|x| x.to_string())
.zip(tup.iter().map(|y| {g = y.iter().map(|q| q.to_string()).collect(); g.clone()}))
.collect();
let keywords = ["ADD", "LIST", "UPDATE", "REMOVE", "HELP", "EXIT"];
// loop the input part of the text interface.
//validate first keyword, send queries to functions.
loop {
let mut query = String::new();
println!("::");
//check for empty input
io::stdin().read_line(&mut query).expect("Enter a valid input");
query = query.trim().to_string();
// println!("{}", query);
if query.is_empty() {
println!("Invalid input. Type HELP for a keyword reference.");
continue;
}
//check for valid first keyword
let keyword = query.split_whitespace().next().unwrap().to_uppercase();
if !keywords.contains(&&keyword[..]) {
println!("Invalid Keyword. Type HELP for a keyword reference.");
continue;
}
//keyword validated. Call the function.
let mut query = query.split_whitespace().collect::<Vec<_>>();
match &&keyword[..] {
&"EXIT" => return,
&"HELP" => help(),
&"ADD" => add(&mut query, &mut company),
&"LIST" => list(&mut query, &mut company),
&"UPDATE" => update(&mut query, &mut company),
&"REMOVE" => remove(&mut query, &mut company),
_ => (),
}
// println!("{:?}", company); //debug purposes: print the entire hashmap on each loop to monitor changes.
continue;
}
}
fn add(q: &mut Vec<&str>, company: &mut HashMap<String, Vec<String>>) {
//validate add syntax
let length = q.len();
if length < 3 || length > 4 {
println!("Invalid ADD syntax. Type HELP for a keyword reference.");
return;
}
//add a new department
if length == 3 {
match (q[0], q[1], q[2]) {
("ADD", "-D", d) => {
//check if dept exists
let dept = d.to_uppercase();
if company.contains_key(&dept) {
println!("Department {} already exists.", d);
return;
}
//add dept
company.entry(dept).or_insert(Vec::new());
println!("Created department {}.", d);
return;
}
_ => {
println!("Invalid syntax.");
return;
}
}
}
//add a person to a department
if length == 4 {
match (q[0], q[1], q[2], q[3]) {
("ADD", name, "TO", d) => {
//check if dept exists
let dept = d.to_uppercase();
if !company.contains_key(&dept) {
println!("Department {} does not exist.", d);
return;
}
//check if name already exists in dept
if company[&dept].contains(&name.to_owned()) {
println!("The name {} already exists in {}.", name, dept);
return;
}
//add name to vector
(*company.get_mut(&dept).unwrap()).push(name.to_owned());
println!("Added {} to {}.", name, d);
}
_ => {
println!("Invalid Syntax");
return;
}
}
}
}
fn list(q: &mut Vec<&str>, company: &mut HashMap<String, Vec<String>>) {
//sanitize input
let length = q.len();
if length != 2 && length !=4 {
println!("Invalid number of arguments.");
return;
}
if length == 2 {
match (q[0], q[1]) {
//list all depts
("LIST", "-D") => {
let mut depts: Vec<_> = company.keys().collect();
depts.sort();
for d in depts {
println!("{}", d);
}
return;
}
//list everyone in all depts, sorted alphabetically
("LIST", "-E") => {
for (dept, mut names) in company.clone() {
println!("---{}---", dept);
names.sort();
for name in names {
println!("{}", name);
}
}
}
_ => {
println!("Invalid Syntax.");
return;
}
}
}
if length == 4 {
match (q[0], q[1], q[2], q[3]) {
("LIST", "-E", "IN", d) => {
//check if dept exists
let dept = d.to_uppercase();
if !company.contains_key(&dept) {
println!("Department {} does not exist.", d);
return;
}
//list all in department
println!("---{}---", dept);
(*company.get_mut(&dept).unwrap()).sort();
for name in &company[&dept] {
println!("{}", name);
}
}
_ => {
println!("Invalid Syntax.");
return;
}
}
}
}
fn update(q: &mut Vec<&str>, company: &mut HashMap<String, Vec<String>>) {
let length = q.len();
if length != 5 && length != 6 {
println!("Invalid UPDATE syntax.");
return;
}
if length == 5 {
match (q[0], q[1], q[2], q[3], q[4]) {
//update a department
("UPDATE", "-D", old_d, "TO", new_d) => {
//check if dept exists
let old_dept = old_d.to_uppercase();
let new_dept = new_d.to_uppercase();
if !company.contains_key(&old_dept) {
println!("Department {} does not exist.", old_d);
return;
}
if company.contains_key(&new_dept) {
println!("Department {} already exists.", new_d);
return;
}
//rename dept. Technique is to build a new vector with that same name since you
//cannot change the key of a hash map.
let temp_dept = company.get(&old_dept).unwrap().clone();
company.insert(new_dept.to_uppercase(), temp_dept);
company.remove(&old_dept);
println!("Changed Department {} to {}.", old_d, new_d);
return;
}
_ => {
println!("Invalid syntax.");
return;
}
}
}
//change a name in a department
match (q[0], q[1], q[2], q[3], q[4], q[5]) {
("UPDATE", old_name, "FROM", d, "TO", new_name) => {
//check if dept exists
let dept = d.to_uppercase();
if !company.contains_key(&dept) {
println!("Department {} does not exist.", d);
return;
}
//check if old name and new name exist
if !company[&dept].contains(&old_name.to_owned()) {
println!("The name {} does not exist in {}.", old_name, dept);
return;
}
if company[&dept].contains(&new_name.to_owned()) {
println!("The name {} already exists in {}.", new_name, dept);
return;
}
//update the name.
for (i, name) in company[&dept].clone().iter().enumerate() {
if name == old_name {
(*company.get_mut(&dept).unwrap())[i] = new_name.to_owned();
println!("Changed {} in {} to {}.", old_name, dept, new_name);
return;
}
}
}
_ => {
println!("Invalid Syntax.");
return;
}
}
}
fn remove(q: &mut Vec<&str>, company: &mut HashMap<String, Vec<String>>) {
let length = q.len();
if length !=3 && length !=4 {
println!("Invalid REMOVE syntax.");
return;
}
if length == 3 {
match (q[0], q[1], q[2]) {
("REMOVE", "-D", d) => {
//check if dept exists
let dept = d.to_uppercase();
if !company.contains_key(&dept) {
println!("Department {} does not exist.", d);
return;
}
//remove the department.
company.remove(&dept);
println!("Removed department {}.", d);
return;
}
_ => {
println!("Invalid Syntax.");
return;
}
}
}
//remove a person
match (q[0], q[1], q[2], q[3]) {
("REMOVE", name, "FROM", d) => {
//check if dept exists
let dept = d.to_uppercase();
if !company.contains_key(&dept) {
println!("Department {} does not exist.", d);
return;
}
//check if name exists
if !company[&dept].contains(&name.to_owned()) {
println!("The name {} does not exist in {}.", name, dept);
return;
}
//remove the name
for (i, _name) in company[&dept].clone().iter().enumerate() {
if _name == name {
(*company.get_mut(&dept).unwrap()).remove(i);
println!("Removed {} from {}.", name, dept);
return;
}
}
}
_ => {
println!("Invalid Syntax.");
return;
}
}
}
fn help() {
println!("The Office - KEYWORD HELP");
println!();
println!("Note: All keywords are case-sensitive.");
println!("Keywords: \nLIST - Lists items in the database");
println!("Usage: LIST -E - Lists all employees");
println!(" LIST -E IN [DEPARTMENT] - Lists all employees in specified department.");
println!(" LIST -D - Lists all departmnets in the company");
println!();
println!("ADD - Adds items to the database.");
println!("Usage: ADD [name] TO [department] - Adds the name to the specified department.");
println!(" ADD -D [department] - Adds the department to the roster.");
println!();
println!("REMOVE - Removes items from the database.");
println!(" REMOVE -D [department] - Removes the particular department from the database.");
println!(" REMOVE [name] FROM [department] - Removes the person from the specified department.");
println!();
println!("UPDATE - Changes records in the database.");
println!("Usage: UPDATE -D [old name] TO [new name] - Changes a department's name.");
println!(" UPDATE [old name] FROM [department] TO [new name] - Changes a person's name.");
println!();
println!("HELP - Prints this help screen.");
println!();
println!("EXIT - Exits the program.")
}
cargo fmt
in your project root. This will run rustfmt which will automatically format your code according to the official code style. If you want, you can configure rustfmt to behave differently \$\endgroup\$