Rustlings Hashmaps 3 Exercise [closed] - rust

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 days ago.
Improve this question
I have written a solution that works for Rustlings Hashmaps 3 but its horid(or maybe it isnt I dont know)
Whats the optimal solution for it?
Please explain simply, im an idiot
The Question:
A list of scores (one per line) of a soccer match is given. Each line
is of the form :
<team_1_name>,<team_2_name>,<team_1_goals>,<team_2_goals>
Example: England,France,4,2 (England scored 4 goals, France 2).
You have to build a scores table containing the name of the team, goals
the team scored, and goals the team conceded. One approach to build
the scores table is to use a Hashmap. The solution is partially
written to use a Hashmap, complete it to pass the test.
Make me pass the tests!
// I AM NOT DONE
use std::collections::HashMap;
// A structure to store team name and its goal details.
struct Team {
name: String,
goals_scored: u8,
goals_conceded: u8,
}
fn build_scores_table(results: String) -> HashMap<String, Team> {
// The name of the team is the key and its associated struct is the value.
let mut scores: HashMap<String, Team> = HashMap::new();
for r in results.lines() {
let v: Vec<&str> = r.split(',').collect();
let team_1_name = v[0].to_string();
let team_1_score: u8 = v[2].parse().unwrap();
let team_2_name = v[1].to_string();
let team_2_score: u8 = v[3].parse().unwrap();
//create both objects
let mut team1 = Team{
name:team_1_name,
goals_scored:team_1_score,
goals_conceded:team_2_score,
};
let mut team2 = Team{
name:team_2_name,
goals_scored:team_2_score,
goals_conceded:team_1_score,
};
//if it isnt in the hashmap add it
if !scores.contains_key(&team1.name){
scores.insert(team1.name.to_owned(),team1);
}
// if it is in the hashmap get it, add values, then insert it back in.
else{
let score = scores.get(&team1.name).unwrap();
team1.goals_scored = team1.goals_scored+ score.goals_scored;
team1.goals_conceded = team1.goals_conceded+ score.goals_conceded;
scores.insert(team1.name.to_owned(),team1);
}
if !scores.contains_key(&team2.name){
scores.insert(team2.name.to_owned(),team2);
}
else{
let score = scores.get(&team2.name).unwrap();
team2.goals_scored = team2.goals_scored+ score.goals_scored;
team2.goals_conceded = team2.goals_conceded+ score.goals_conceded;
scores.insert(team2.name.to_owned(),team2);
}
// TODO: Populate the scores table with details extracted from the
// current line. Keep in mind that goals scored by team_1
// will be the number of goals conceded from team_2, and similarly
// goals scored by team_2 will be the number of goals conceded by
// team_1.
}
scores
}
#[cfg(test)]
mod tests {
use super::*;
fn get_results() -> String {
let results = "".to_string()
+ "England,France,4,2\n"
+ "France,Italy,3,1\n"
+ "Poland,Spain,2,0\n"
+ "Germany,England,2,1\n";
results
}
#[test]
fn build_scores() {
let scores = build_scores_table(get_results());
let mut keys: Vec<&String> = scores.keys().collect();
keys.sort();
assert_eq!(
keys,
vec!["England", "France", "Germany", "Italy", "Poland", "Spain"]
);
}
#[test]
fn validate_team_score_1() {
let scores = build_scores_table(get_results());
let team = scores.get("England").unwrap();
assert_eq!(team.goals_scored, 5);
assert_eq!(team.goals_conceded, 4);
}
#[test]
fn validate_team_score_2() {
let scores = build_scores_table(get_results());
let team = scores.get("Spain").unwrap();
assert_eq!(team.goals_scored, 0);
assert_eq!(team.goals_conceded, 2);
}
}
Need the optimal solution for hashmaps exercise 3

Related

Why Sequential is faster than parallel?

The code is to count the frequency of each word in an article. In the code, I implemented sequential, muti-thread, and muti-thread with a thread pool.
I test the running time of three methods, however, I found that the sequential method is the fastest one. I use the article (data) at 37423.txt, the code is at play.rust-lang.org.
Below is just the single- and multi version (without the threadpool version):
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::SystemTime;
pub fn word_count(article: &str) -> HashMap<String, i64> {
let now1 = SystemTime::now();
let mut map = HashMap::new();
for word in article.split_whitespace() {
let count = map.entry(word.to_string()).or_insert(0);
*count += 1;
}
let after1 = SystemTime::now();
let d1 = after1.duration_since(now1);
println!("single: {:?}", d1.as_ref().unwrap());
map
}
fn word_count_thread(word_vec: Vec<String>, counts: &Arc<Mutex<HashMap<String, i64>>>) {
let mut p_count = HashMap::new();
for word in word_vec {
*p_count.entry(word).or_insert(0) += 1;
}
let mut counts = counts.lock().unwrap();
for (word, count) in p_count {
*counts.entry(word.to_string()).or_insert(0) += count;
}
}
pub fn mt_word_count(article: &str) -> HashMap<String, i64> {
let word_vec = article
.split_whitespace()
.map(|x| x.to_owned())
.collect::<Vec<String>>();
let count = Arc::new(Mutex::new(HashMap::new()));
let len = word_vec.len();
let q1 = len / 4;
let q2 = len / 2;
let q3 = q1 * 3;
let part1 = word_vec[..q1].to_vec();
let part2 = word_vec[q1..q2].to_vec();
let part3 = word_vec[q2..q3].to_vec();
let part4 = word_vec[q3..].to_vec();
let now2 = SystemTime::now();
let count1 = count.clone();
let count2 = count.clone();
let count3 = count.clone();
let count4 = count.clone();
let handle1 = thread::spawn(move || {
word_count_thread(part1, &count1);
});
let handle2 = thread::spawn(move || {
word_count_thread(part2, &count2);
});
let handle3 = thread::spawn(move || {
word_count_thread(part3, &count3);
});
let handle4 = thread::spawn(move || {
word_count_thread(part4, &count4);
});
handle1.join().unwrap();
handle2.join().unwrap();
handle3.join().unwrap();
handle4.join().unwrap();
let x = count.lock().unwrap().clone();
let after2 = SystemTime::now();
let d2 = after2.duration_since(now2);
println!("muti: {:?}", d2.as_ref().unwrap());
x
}
The result of mine is: single:7.93ms, muti: 15.78ms, threadpool: 25.33ms
I did the separation of the article before calculating time.
I want to know if the code has some problem.
First you may want to know the single-threaded version is mostly parsing whitespace (and I/O, but the file is small so it will be in the OS cache on the second run). At most ~20% of the runtime is the counting that you parallelized. Here is the cargo flamegraph of the single-threaded code:
In the multi-threaded version, it's a mess of thread creation, copying and hashmap overhead. To make sure it's not a "too little data" problem, I've used used 100x your input txt file and I'm measuring a 2x slowdown over the single-threaded version. According to the time command, it uses 2x CPU-time compared to wall-clock, so it seems to do some parallel work. The profile looks like this: (clickable svg version)
I'm not sure what to make of it exactly, but it's clear that memory management overhead has increased a lot. You seem to be copying strings for each hashmap, while an ideal wordcount would probably do zero string copying while counting.
More generally I think it's a bad idea to join the results in the threads - the way you do it (as opposed to a map-reduce pattern) the thread needs a global lock, so you could just pass the results back to the main thread instead for combining. I'm not sure if this is the main problem here, though.
Optimization
To avoid string copying, use HashMap<&str, i64> instead of HashMap<String, i64>. This requires some changes (lifetime annotations and thread::scope()). It makes mt_word_count() about 6x faster compared to the old version.
With a large input I'm measuring now a 4x speedup compared to word_count(). (Which is the best you can hope for with four threads.) The multi-threaded version is now also faster overall, but only by ~20% or so, for the reasons explained above. (Note that the single-threaded baseline has also profited the same &str optimization. Also, many things could still be improved/optimized, but I'll stop here.)
fn word_count_thread<'t>(word_vec: Vec<&'t str>, counts: &Arc<Mutex<HashMap<&'t str, i64>>>) {
let mut p_count = HashMap::new();
for word in word_vec {
*p_count.entry(word).or_insert(0) += 1;
}
let mut counts = counts.lock().unwrap();
for (word, count) in p_count {
*counts.entry(word).or_insert(0) += count;
}
}
pub fn mt_word_count<'t>(article: &'t str) -> HashMap<&'t str, i64> {
let word_vec = article.split_whitespace().collect::<Vec<&str>>();
// (skipping 16 unmodified lines)
let x = thread::scope(|scope| {
let handle1 = scope.spawn(move || {
word_count_thread(part1, &count1);
});
let handle2 = scope.spawn(move || {
word_count_thread(part2, &count2);
});
let handle3 = scope.spawn(move || {
word_count_thread(part3, &count3);
});
let handle4 = scope.spawn(move || {
word_count_thread(part4, &count4);
});
handle1.join().unwrap();
handle2.join().unwrap();
handle3.join().unwrap();
handle4.join().unwrap();
count.lock().unwrap().clone()
});
let after2 = SystemTime::now();
let d2 = after2.duration_since(now2);
println!("muti: {:?}", d2.as_ref().unwrap());
x
}

My Rust OR_INSERT Hashmap code to update a struct content works without dereferencing. Why?

From Rust documentation, this count variable wouldn't work without dereferencing (*)
let text = "hello world wonderful world";
let mut map = HashMap::new();
for word in text.split_whitespace() {
let count = map.entry(word).or_insert(0);
*count += 1;
}
println!("{:?}", map);
However, I have the following code which tries to update a u8 variable (i.e team_val.goals_scored ) in a Struct if the key is found in a hashmap. It works without dereferencing. My understanding from the above Rust documentation was I need to dereference the team_val.goals_scored in order to update the content of the struct which is also a value for the hash map. Whelp!
My code:
#[derive(Debug)]
struct Team {
name: String,
goals_scored: u8,
goals_conceded: u8,
}
fn build_scores_table(results: String) -> HashMap<String, Team> {
// The name of the team is the key and its associated struct is the value.
let mut scores: HashMap<String, Team> = HashMap::new();
for r in results.lines() {
let v: Vec<&str> = r.split(',').collect();
let team_1_name = v[0].to_string();
let team_1_score: u8 = v[2].parse().unwrap();
let team_2_name = v[1].to_string();
let team_2_score: u8 = v[3].parse().unwrap();
// TODO: Populate the scores table with details extracted from the
// current line. Keep in mind that goals scored by team_1
// will be number of goals conceded from team_2, and similarly
// goals scored by team_2 will be the number of goals conceded by
// team_1.
let mut team_1_struct= Team {
name: team_1_name.clone(),
goals_scored: team_1_score,
goals_conceded: team_2_score
};
let mut team_2_struct= Team {
name: team_2_name.clone(),
goals_scored: team_2_score,
goals_conceded: team_1_score
};
if scores.contains_key(&team_1_name) {
let team_val = scores.entry(team_1_name.clone()).or_insert(team_1_struct);
println!("Yooo {:#?}",team_val);
team_val.goals_scored +=team_1_score;
team_val.goals_conceded += team_2_score;
} else {
scores.insert(team_1_name,team_1_struct);
}
if scores.contains_key(&team_2_name) {
let team_val = scores.entry(team_2_name.clone()).or_insert(team_2_struct);
println!("Yooo {:#?}",team_val);
team_val.goals_scored +=team_2_score;
team_val.goals_conceded += team_1_score;
} else {
scores.insert(team_2_name,team_2_struct);
}
}
scores
}
Rust does some automatic dereferencing, described here. We can see the difference between the documentation code and what you wrote:
// This
*count += 1
// versus this
team_val.goals_scored += team_1_score
^--- Causes an automatic dereferencing
If you're coming from C I think this documentation may be even clearer.
Answering the follow-up question 'can you use entry() without using clone() on the key - you cannot. entry() consumes what you send it, and doesn't give it back, so the borrow checker will prevent you from doing this. It's currently a 'limitation' on the API - but if you're dealing with something as cheap as a short string to copy, then this shouldn't impact you much.
You can do a fair amount to slim down your code, though (with the caveat that I only did this for one team - it's easily extensible):
use std::collections::HashMap;
struct Team {
name: String,
goals: u8
}
type Scorecard = HashMap<String, Team>;
fn scoring(records: String) -> Scorecard {
let mut scores : Scorecard = HashMap::new();
for r in records.lines() {
let record: Vec<&str> = r.split(',').collect();
let team_name = record[0].to_string();
let team_score: u8 = record[1].parse().unwrap();
// Note that we do not need to create teams here on every iteration.
// There is a chance they already exist, and we can create them only if
// the `or_insert()` clause is activated.
// Note that the `entry()` clause when used with `or_insert()`
// gives you an implicit 'does this key exist?' check.
let team = scores.entry(team_name.clone()).or_insert(Team {
name: team_name,
goals: 0,
});
team.goals += team_score;
}
scores
}
fn main() {
let record = "Thunderers,1\nFlashians,1\nThunderers,2";
let scores = scoring(record.to_string());
for (key, value) in &scores {
println!("{}: The mighty {} scored {} points.", key, value.name, value.goals)
}
// Flattening Some Options!
// let o = Some(Some(5));
// let p = Some(5);
// println!("o: {:#?}", o);
// println!("p: {:#?}", p);
// println!("flattened o: {:#?}", o.flatten());
// println!("flattened p: {:#?}", p.flatten());
}

Cannot borrow `scores` as mutable more than once?

An exercise gives me football matches, with teams and their scores (like: "England,France,4,2"), and I have to insert the teams in a hashmap with its goals scored and conceded. The key is the name of the team and I pass the goals scored and conceded as a struct.
The exercise then gives me another string with a team already in the hashmap ("England,Spain,1,3") and I have to update the hashmap on the England key, adding the goals from that match. How can I do that?
My code (playground):
// hashmaps3.rs
// A list of scores (one per line) of a soccer match is given. Each line
// is of the form :
// <team_1_name>,<team_2_name>,<team_1_goals>,<team_2_goals>
// Example: England,France,4,2 (England scored 4 goals, France 2).
// You have to build a scores table containing the name of the team, goals
// the team scored, and goals the team conceded. One approach to build
// the scores table is to use a Hashmap. The solution is partially
// written to use a Hashmap, complete it to pass the test.
// Make me pass the tests!
// Execute `rustlings hint hashmaps3` or use the `hint` watch subcommand for a hint.
// I AM NOT DONE
use std::collections::HashMap;
// A structure to store team name and its goal details.
struct Team {
name: String,
goals_scored: u8,
goals_conceded: u8,
}
fn build_scores_table(results: String) -> HashMap<String, Team> {
// The name of the team is the key and its associated struct is the value.
let mut scores: HashMap<String, Team> = HashMap::new();
for r in results.lines() {
let v: Vec<&str> = r.split(',').collect();
let team_1_name = v[0].to_string();
let team_1_score: u8 = v[2].parse().unwrap();
let team_2_name = v[1].to_string();
let team_2_score: u8 = v[3].parse().unwrap();
let team_1 = Team {
name: team_1_name.clone(),
goals_scored: team_1_score,
goals_conceded: team_2_score,
};
let team_2 = Team {
name: team_2_name.clone(),
goals_scored: team_2_score,
goals_conceded: team_1_score,
};
let team1 = scores.entry(team_1_name).or_insert(team_1);
let team2 = scores.entry(team_2_name).or_insert(team_2);
team1.goals_scored += team_1_score;
team1.goals_conceded += team_2_score;
team2.goals_scored += team_2_score;
team2.goals_conceded += team_1_score;
}
scores
}
#[cfg(test)]
mod tests {
use super::*;
fn get_results() -> String {
let results = "".to_string()
+ "England,France,4,2\n"
+ "France,Italy,3,1\n"
+ "Poland,Spain,2,0\n"
+ "Germany,England,2,1\n";
results
}
#[test]
fn build_scores() {
let scores = build_scores_table(get_results());
let mut keys: Vec<&String> = scores.keys().collect();
keys.sort();
assert_eq!(
keys,
vec!["England", "France", "Germany", "Italy", "Poland", "Spain"]
);
}
#[test]
fn validate_team_score_1() {
let scores = build_scores_table(get_results());
let team = scores.get("England").unwrap();
assert_eq!(team.goals_scored, 5);
assert_eq!(team.goals_conceded, 4);
}
#[test]
fn validate_team_score_2() {
let scores = build_scores_table(get_results());
let team = scores.get("Spain").unwrap();
assert_eq!(team.goals_scored, 0);
assert_eq!(team.goals_conceded, 2);
}
}
Error:
error[E0499]: cannot borrow `scores` as mutable more than once at a time
--> src/lib.rs:49:21
|
48 | let team1 = scores.entry(team_1_name).or_insert(team_1);
| ------------------------- first mutable borrow occurs here
49 | let team2 = scores.entry(team_2_name).or_insert(team_2);
| ^^^^^^^^^^^^^^^^^^^^^^^^^ second mutable borrow occurs here
50 | team1.goals_scored += team_1_score;
| ---------------------------------- first borrow later used here
I think I understand the error, so I have tried using
let team1 = &mut scores.entry.... That is spitting some weird errors. So I am lost here, any help would be much appreciated.
You can't borrow twice mutable from the same HashMap. The entry() call borrows from HashMap.
To fix this, just don't do it simultaneously. Borrow one, mutate, and then borrow the other.
// hashmaps3.rs
// A list of scores (one per line) of a soccer match is given. Each line
// is of the form :
// <team_1_name>,<team_2_name>,<team_1_goals>,<team_2_goals>
// Example: England,France,4,2 (England scored 4 goals, France 2).
// You have to build a scores table containing the name of the team, goals
// the team scored, and goals the team conceded. One approach to build
// the scores table is to use a Hashmap. The solution is partially
// written to use a Hashmap, complete it to pass the test.
// Make me pass the tests!
// Execute `rustlings hint hashmaps3` or use the `hint` watch subcommand for a hint.
// I AM NOT DONE
use std::collections::HashMap;
// A structure to store team name and its goal details.
struct Team {
name: String,
goals_scored: u8,
goals_conceded: u8,
}
fn build_scores_table(results: String) -> HashMap<String, Team> {
// The name of the team is the key and its associated struct is the value.
let mut scores: HashMap<String, Team> = HashMap::new();
for r in results.lines() {
let v: Vec<&str> = r.split(',').collect();
let team_1_name = v[0].to_string();
let team_1_score: u8 = v[2].parse().unwrap();
let team_2_name = v[1].to_string();
let team_2_score: u8 = v[3].parse().unwrap();
let team_1 = Team {
name: team_1_name.clone(),
goals_scored: team_1_score,
goals_conceded: team_2_score,
};
let team_2 = Team {
name: team_2_name.clone(),
goals_scored: team_2_score,
goals_conceded: team_1_score,
};
let team1 = scores.entry(team_1_name).or_insert(team_1);
team1.goals_scored += team_1_score;
team1.goals_conceded += team_2_score;
let team2 = scores.entry(team_2_name).or_insert(team_2);
team2.goals_scored += team_2_score;
team2.goals_conceded += team_1_score;
}
scores
}
#[cfg(test)]
mod tests {
use super::*;
fn get_results() -> String {
let results = "".to_string()
+ "England,France,4,2\n"
+ "France,Italy,3,1\n"
+ "Poland,Spain,2,0\n"
+ "Germany,England,2,1\n";
results
}
#[test]
fn build_scores() {
let scores = build_scores_table(get_results());
let mut keys: Vec<&String> = scores.keys().collect();
keys.sort();
assert_eq!(
keys,
vec!["England", "France", "Germany", "Italy", "Poland", "Spain"]
);
}
#[test]
fn validate_team_score_1() {
let scores = build_scores_table(get_results());
let team = scores.get("England").unwrap();
assert_eq!(team.goals_scored, 5);
assert_eq!(team.goals_conceded, 4);
}
#[test]
fn validate_team_score_2() {
let scores = build_scores_table(get_results());
let team = scores.get("Spain").unwrap();
assert_eq!(team.goals_scored, 0);
assert_eq!(team.goals_conceded, 2);
}
}
That said, there are still problems with your code. Mainly that you write the goals to the team at or_insert(), and then add them again.
If you insert, insert with an empty score, as you will add the goals later.
// hashmaps3.rs
// A list of scores (one per line) of a soccer match is given. Each line
// is of the form :
// <team_1_name>,<team_2_name>,<team_1_goals>,<team_2_goals>
// Example: England,France,4,2 (England scored 4 goals, France 2).
// You have to build a scores table containing the name of the team, goals
// the team scored, and goals the team conceded. One approach to build
// the scores table is to use a Hashmap. The solution is partially
// written to use a Hashmap, complete it to pass the test.
// Make me pass the tests!
// Execute `rustlings hint hashmaps3` or use the `hint` watch subcommand for a hint.
// I AM NOT DONE
use std::collections::HashMap;
// A structure to store team name and its goal details.
struct Team {
name: String,
goals_scored: u8,
goals_conceded: u8,
}
fn build_scores_table(results: String) -> HashMap<String, Team> {
// The name of the team is the key and its associated struct is the value.
let mut scores: HashMap<String, Team> = HashMap::new();
for r in results.lines() {
let v: Vec<&str> = r.split(',').collect();
let team_1_name = v[0].to_string();
let team_1_score: u8 = v[2].parse().unwrap();
let team_2_name = v[1].to_string();
let team_2_score: u8 = v[3].parse().unwrap();
let team_1 = Team {
name: team_1_name.clone(),
goals_scored: 0,
goals_conceded: 0,
};
let team_2 = Team {
name: team_2_name.clone(),
goals_scored: 0,
goals_conceded: 0,
};
let team1 = scores.entry(team_1_name).or_insert(team_1);
team1.goals_scored += team_1_score;
team1.goals_conceded += team_2_score;
let team2 = scores.entry(team_2_name).or_insert(team_2);
team2.goals_scored += team_2_score;
team2.goals_conceded += team_1_score;
}
scores
}
#[cfg(test)]
mod tests {
use super::*;
fn get_results() -> String {
let results = "".to_string()
+ "England,France,4,2\n"
+ "France,Italy,3,1\n"
+ "Poland,Spain,2,0\n"
+ "Germany,England,2,1\n";
results
}
#[test]
fn build_scores() {
let scores = build_scores_table(get_results());
let mut keys: Vec<&String> = scores.keys().collect();
keys.sort();
assert_eq!(
keys,
vec!["England", "France", "Germany", "Italy", "Poland", "Spain"]
);
}
#[test]
fn validate_team_score_1() {
let scores = build_scores_table(get_results());
let team = scores.get("England").unwrap();
assert_eq!(team.goals_scored, 5);
assert_eq!(team.goals_conceded, 4);
}
#[test]
fn validate_team_score_2() {
let scores = build_scores_table(get_results());
let team = scores.get("Spain").unwrap();
assert_eq!(team.goals_scored, 0);
assert_eq!(team.goals_conceded, 2);
}
}
Further, you can extract the creation of a new team with empty scores into a separate function. This makes your code a lot cleaner. Further, you can use .or_insert_with() to run the initialization code only when needed.
// hashmaps3.rs
// A list of scores (one per line) of a soccer match is given. Each line
// is of the form :
// <team_1_name>,<team_2_name>,<team_1_goals>,<team_2_goals>
// Example: England,France,4,2 (England scored 4 goals, France 2).
// You have to build a scores table containing the name of the team, goals
// the team scored, and goals the team conceded. One approach to build
// the scores table is to use a Hashmap. The solution is partially
// written to use a Hashmap, complete it to pass the test.
// Make me pass the tests!
// Execute `rustlings hint hashmaps3` or use the `hint` watch subcommand for a hint.
// I AM NOT DONE
use std::collections::HashMap;
// A structure to store team name and its goal details.
struct Team {
name: String,
goals_scored: u8,
goals_conceded: u8,
}
impl Team {
fn new(name: &str) -> Self {
Self {
name: name.to_string(),
goals_scored: 0,
goals_conceded: 0,
}
}
}
fn build_scores_table(results: String) -> HashMap<String, Team> {
// The name of the team is the key and its associated struct is the value.
let mut scores: HashMap<String, Team> = HashMap::new();
for r in results.lines() {
let v: Vec<&str> = r.split(',').collect();
let team_1_name = v[0].to_string();
let team_1_score: u8 = v[2].parse().unwrap();
let team_2_name = v[1].to_string();
let team_2_score: u8 = v[3].parse().unwrap();
let team1 = scores
.entry(team_1_name.clone())
.or_insert_with(|| Team::new(&team_1_name));
team1.goals_scored += team_1_score;
team1.goals_conceded += team_2_score;
let team2 = scores
.entry(team_2_name.clone())
.or_insert_with(|| Team::new(&team_2_name));
team2.goals_scored += team_2_score;
team2.goals_conceded += team_1_score;
}
scores
}
#[cfg(test)]
mod tests {
use super::*;
fn get_results() -> String {
let results = "".to_string()
+ "England,France,4,2\n"
+ "France,Italy,3,1\n"
+ "Poland,Spain,2,0\n"
+ "Germany,England,2,1\n";
results
}
#[test]
fn build_scores() {
let scores = build_scores_table(get_results());
let mut keys: Vec<&String> = scores.keys().collect();
keys.sort();
assert_eq!(
keys,
vec!["England", "France", "Germany", "Italy", "Poland", "Spain"]
);
}
#[test]
fn validate_team_score_1() {
let scores = build_scores_table(get_results());
let team = scores.get("England").unwrap();
assert_eq!(team.goals_scored, 5);
assert_eq!(team.goals_conceded, 4);
}
#[test]
fn validate_team_score_2() {
let scores = build_scores_table(get_results());
let team = scores.get("Spain").unwrap();
assert_eq!(team.goals_scored, 0);
assert_eq!(team.goals_conceded, 2);
}
}

Correct answer at rustlings course, but not happy with it

This is complaining on highest of niveaus, but I solved a task from the rustlings course and I'm confident that this can't be the optimal solution - or even a good one.
The task: https://github.com/rust-lang/rustlings/blob/main/exercises/hashmaps/hashmaps3.rs
My solution (only the the relevant bit):
fn build_scores_table(results: String) -> HashMap<String, Team> {
// The name of the team is the key and its associated struct is the value.
let mut scores: HashMap<String, Team> = HashMap::new();
for r in results.lines() {
let v: Vec<&str> = r.split(',').collect();
let team_1_name = v[0].to_string();
let team_1_score: u8 = v[2].parse().unwrap();
let team_2_name = v[1].to_string();
let team_2_score: u8 = v[3].parse().unwrap();
let team_1 = scores.entry(team_1_name.clone()).or_insert(Team {
name: team_1_name.clone(),
goals_scored: 0,
goals_conceded: 0,
});
team_1.goals_scored += team_1_score;
team_1.goals_conceded += team_2_score;
let team_2 = scores.entry(team_2_name.clone()).or_insert(Team {
name: team_2_name.clone(),
goals_scored: 0,
goals_conceded: 0,
});
team_2.goals_scored += team_2_score;
team_2.goals_conceded += team_1_score;
}
scores
}
My problem is, that I'm cloning the strings (twice!) inside the .entry() method an also in the Team struct. I tried using it without, but it's not working (borrowing stuff) and using the & but it's not happy because it expects String - not &String.
Not sure what's not working exactly? You can just move on the second use site, it works fine:
let team_1 = scores.entry(team_1_name.clone()).or_insert(Team {
name: team_1_name,
goals_scored: 0,
goals_conceded: 0,
});
team_1.goals_scored += team_1_score;
team_1.goals_conceded += team_2_score;
If you want zero cloning in the success case (where there's already an entry for the team) the code is a little less sexy but it also compiles fine:
if let Some(t) = scores.get_mut(&team_1_name) {
t.goals_scored += team_1_score;
t.goals_conceded += team_2_score;
} else {
scores.insert(team_1_name.clone(), Team {
name: team_1_name,
goals_scored: team_1_score,
goals_conceded: team_2_score,
});
}
Technically we can even remove the initial to_string and do no allocation in the hit case (obviously that means two allocations in the no-hit case):
let team_1_name = v[0];
let team_1_score: u8 = v[2].parse().unwrap();
// ...
if let Some(t) = scores.get_mut(team_1_name) {
t.goals_scored += team_1_score;
t.goals_conceded += team_2_score;
} else {
scores.insert(team_1_name.to_string(), Team {
name: team_1_name.to_string(),
goals_scored: team_1_score,
goals_conceded: team_2_score,
});
}
Alternatively, remove the name from the Team struct, it's not really valuable since you have the hashmap key. Though at this point you don't have a Team struct anymore so it should probably be renamed e.g. Score or Goals (and you can strip the prefix from the two members left).
You don't need the second .clone() calls (in the Team), because you can allow the struct to take ownership of that one, as nothing else uses it afterwards. They set the hashmap up kinda weird for you, since the key and the value both contain the team name. If you just remove it from the struct definition, there isn't any more need for cloning (it is never used, anyways).
struct Team {
goals_scored: u8,
goals_conceded: u8,
}
fn build_scores_table(results: String) -> HashMap<String, Team> {
// The name of the team is the key and its associated struct is the value.
let mut scores: HashMap<String, Team> = HashMap::new();
for r in results.lines() {
let v: Vec<&str> = r.split(',').collect();
let team_1_name = v[0].to_string();
let team_1_score: u8 = v[2].parse().unwrap();
let team_2_name = v[1].to_string();
let team_2_score: u8 = v[3].parse().unwrap();
let team_1 = scores.entry(team_1_name).or_insert(Team {
goals_scored: 0,
goals_conceded: 0,
});
team_1.goals_scored += team_1_score;
team_1.goals_conceded += team_2_score;
let team_2 = scores.entry(team_2_name).or_insert(Team {
goals_scored: 0,
goals_conceded: 0,
});
team_2.goals_scored += team_2_score;
team_2.goals_conceded += team_1_score;
}
scores
}
This is ultimately what I ended up with, not crazy about having to use "and_modify" twice, but c'est la vie.
fn build_scores_table(results: String) -> HashMap<String, Team> {
let mut scores: HashMap<String, Team> = HashMap::new();
for r in results.lines() {
let v: Vec<&str> = r.split(',').collect();
let team_1_name = v[0].to_string();
let team_1_score: u8 = v[2].parse().unwrap();
let team_2_name = v[1].to_string();
let team_2_score: u8 = v[3].parse().unwrap();
scores.entry(team_1_name.clone())
.and_modify(|a| a.goals_scored += team_1_score)
.and_modify(|b| b.goals_conceded += team_2_score)
.or_insert(Team { name: team_1_name,
goals_scored: team_1_score,
goals_conceded: team_2_score });
scores.entry(team_2_name.clone())
.and_modify(|a| a.goals_scored += team_2_score)
.and_modify(|b| b.goals_conceded += team_1_score)
.or_insert(Team { name: team_2_name,
goals_scored: team_2_score,
goals_conceded: team_1_score });
}
scores
}
Learnt something! Thank you!
BTW, I have a follow up question: In contrast to the Rust book, why can't we add (*) to this line
team_1.goals_scored += team_1_score;
to become
*team_1.goals_scored += team_1_score;
Any reason it fails?
I think it is somewhat related to the fact that is a type Team instead of primitive type as value of hashmap, but I still wish have a clear understanding about that.
In regards to the follow up question, have you tried the following:
(*team_1).goals_scored += team_1_score;

Group vector of structs by field

I want to create a vector with all of the matching field id from the struct, process that new vector and then repeat the process. Basically grouping together the structs with matching field id.
Is there a way to do this by not using the unstable feature drain_filter?
#![feature(drain_filter)]
#[derive(Debug)]
struct Person {
id: u32,
}
fn main() {
let mut people = vec![];
for p in 0..10 {
people.push(Person { id: p });
}
while !people.is_empty() {
let first_person_id = people.first().unwrap().id;
let drained: Vec<Person> = people.drain_filter(|p| p.id == first_person_id).collect();
println!("{:#?}", drained);
}
}
Playground
If you are looking to group your vector by the person id, it's likely to be more efficient using a HashMap from id to Vec<Person>, where each id hold a vector of persons. And then you can loop through the HashMap and process each vector / group. This is potentially more efficient than draining people in each iteration, which in worst case has O(N^2) time complexity while with a HashMap the time complexity is O(N).
#![feature(drain_filter)]
use std::collections::HashMap;
#[derive(Debug)]
struct Person {
id: u32,
}
fn main() {
let mut people = vec![];
let mut groups: HashMap<u32, Vec<Person>> = HashMap::new();
for p in 0..10 {
people.push(Person { id: p });
}
people.into_iter().for_each(|person| {
let group = groups.entry(person.id).or_insert(vec![]);
group.push(person);
});
for (_id, group) in groups {
println!("{:#?}", group);
}
}
Playground

Resources