-1

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • This [seems to work on the playground.](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=a240fc2653fb86d292d1115c6bef39bb) Could you add more information (e.g. the complete error message)? – c-x-berger Sep 12 '22 at 17:45
  • It looks like your question might be answered by the answers of [Borrow two mutable values from the same HashMap](https://stackoverflow.com/q/47773849/155423); [How can I mutate other elements of a HashMap when using the entry pattern?](https://stackoverflow.com/q/52846885/155423); and a number of others. If not, please **[edit]** your question to explain the differences. Otherwise, we can mark this question as already answered. – Shepmaster Sep 12 '22 at 18:15
  • Doesn't your code count goals twice if `or_insert` inserted them into the map? – Finomnis Sep 12 '22 at 18:19
  • For fixing your code: don't use both `entry` values simultaneously. Instead, query the first one, set its values, then query the second one. You can't borrow from a map twice mutably at the same time. – Finomnis Sep 12 '22 at 18:21
  • Thanks @Finomnis, it did solved the error. It was still failing the third test, but changing the initial scores to 0, passed it. – Rajesh Majumdar Sep 12 '22 at 18:35

1 Answers1

0

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);
    }
}
Finomnis
  • 18,094
  • 1
  • 20
  • 27