# Hashmaps

* The type `HashMap<K, V>` stores a mapping of keys of type `K` to values of type `V` using a hashing function, which determines how it places these keys and values into memory.
* For types that implement the `Copy` trait, like `i32`, the values are copied into the hash map. For owned values like `String`, the values will be moved and the hash map will be the owner of those values.
* Reference:
  * <https://doc.rust-lang.org/book/ch08-03-hash-maps.html>
  * <https://doc.rust-lang.org/beta/std/collections/hash_map/enum.Entry.html>

## hashmaps1.rs

```rust
// A basket of fruits in the form of a hash map needs to be defined. The key
// represents the name of the fruit and the value represents how many of that
// particular fruit is in the basket. You have to put at least 3 different
// types of fruits (e.g. apple, banana, mango) in the basket and the total count
// of all the fruits should be at least 5.

use std::collections::HashMap;

fn fruit_basket() -> HashMap<String, u32> {
    // TODO: Declare the hash map.
    let mut basket = HashMap::new();

    // Two bananas are already given for you :)
    basket.insert(String::from("banana"), 2);
    basket.insert(String::from("apple"), 5);
    basket.insert(String::from("mango"), 10);

    basket
}

fn main() {
    // You can optionally experiment here.
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn at_least_three_types_of_fruits() {
        let basket = fruit_basket();
        assert!(basket.len() >= 3);
    }

    #[test]
    fn at_least_five_fruits() {
        let basket = fruit_basket();
        assert!(basket.values().sum::<u32>() >= 5);
    }
}
```

* I this exercise we just need to declare new `HashMap` and insert more entries into it until at least we have `5` total values.
* We can define new `HashMap` like this:

  ```rust
  let mut basket = HashMap::new();
  ```
* As you can see we are not defining any types explicitly here because rust will infer it.
* But we need to make sure all the keys have the same type and all the values also have the same types.
* The we need to add more entries into the hashmap by using `insert` method.

## hashmaps2.rs

```rust
// We're collecting different fruits to bake a delicious fruit cake. For this,
// we have a basket, which we'll represent in the form of a hash map. The key
// represents the name of each fruit we collect and the value represents how
// many of that particular fruit we have collected. Three types of fruits -
// Apple (4), Mango (2) and Lychee (5) are already in the basket hash map. You
// must add fruit to the basket so that there is at least one of each kind and
// more than 11 in total - we have a lot of mouths to feed. You are not allowed
// to insert any more of the fruits that are already in the basket (Apple,
// Mango, and Lychee).

use std::collections::HashMap;

#[derive(Hash, PartialEq, Eq, Debug)]
enum Fruit {
    Apple,
    Banana,
    Mango,
    Lychee,
    Pineapple,
}

fn fruit_basket(basket: &mut HashMap<Fruit, u32>) {
    let fruit_kinds = [
        Fruit::Apple,
        Fruit::Banana,
        Fruit::Mango,
        Fruit::Lychee,
        Fruit::Pineapple,
    ];

    for fruit in fruit_kinds {
        // TODO: Insert new fruits if they are not already present in the
        // basket. Note that you are not allowed to put any type of fruit that's
        // already present!
        if basket.get(&fruit).is_none() {
            basket.insert(fruit, 5);
        }
    }
}

fn main() {
    // You can optionally experiment here.
}

#[cfg(test)]
mod tests {
    use super::*;

    // Don't modify this function!
    fn get_fruit_basket() -> HashMap<Fruit, u32> {
        let content = [(Fruit::Apple, 4), (Fruit::Mango, 2), (Fruit::Lychee, 5)];
        HashMap::from_iter(content)
    }

    #[test]
    fn test_given_fruits_are_not_modified() {
        let mut basket = get_fruit_basket();
        fruit_basket(&mut basket);
        assert_eq!(*basket.get(&Fruit::Apple).unwrap(), 4);
        assert_eq!(*basket.get(&Fruit::Mango).unwrap(), 2);
        assert_eq!(*basket.get(&Fruit::Lychee).unwrap(), 5);
    }

    #[test]
    fn at_least_five_types_of_fruits() {
        let mut basket = get_fruit_basket();
        fruit_basket(&mut basket);
        let count_fruit_kinds = basket.len();
        assert!(count_fruit_kinds >= 5);
    }

    #[test]
    fn greater_than_eleven_fruits() {
        let mut basket = get_fruit_basket();
        fruit_basket(&mut basket);
        let count = basket.values().sum::<u32>();
        assert!(count > 11);
    }

    #[test]
    fn all_fruit_types_in_basket() {
        let fruit_kinds = [
            Fruit::Apple,
            Fruit::Banana,
            Fruit::Mango,
            Fruit::Lychee,
            Fruit::Pineapple,
        ];

        let mut basket = get_fruit_basket();
        fruit_basket(&mut basket);

        for fruit_kind in fruit_kinds {
            let Some(amount) = basket.get(&fruit_kind) else {
                panic!("Fruit kind {fruit_kind:?} was not found in basket");
            };
            assert!(*amount > 0);
        }
    }
}
```

* In this example we need to insert new fruits if they are not already present in the basket.
* We can do this in multiple different ways.
* First is by checking if the key exist if not the insert it:

  ```rust
  if basket.get(&fruit).is_none() {
    basket.insert(fruit, 5);
  }
  ```
* Or we can use `Entry` like this:

  ```rust
  basket.entry(fruit).or_insert(5);
  ```
* You can read more about entry in here: <https://doc.rust-lang.org/beta/std/collections/hash_map/enum.Entry.html>.

## hashmaps3.rs

```rust
// 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, the total
// number of goals the team scored, and the total number of goals the team
// conceded.

use std::collections::HashMap;

// A structure to store the goal details of a team.
#[derive(Default)]
struct TeamScores {
    goals_scored: u8,
    goals_conceded: u8,
}

fn build_scores_table(results: &str) -> HashMap<&str, TeamScores> {
    // The name of the team is the key and its associated struct is the value.
    let mut scores = HashMap::<&str, TeamScores>::new();

    for line in results.lines() {
        let mut split_iterator = line.split(',');
        // NOTE: We use `unwrap` because we didn't deal with error handling yet.
        let team_1_name = split_iterator.next().unwrap();
        let team_2_name = split_iterator.next().unwrap();
        let team_1_score: u8 = split_iterator.next().unwrap().parse().unwrap();
        let team_2_score: u8 = split_iterator.next().unwrap().parse().unwrap();

        // TODO: Populate the scores table with the extracted details.
        // Keep in mind that goals scored by team 1 will be the number of goals
        // conceded by team 2. Similarly, goals scored by team 2 will be the
        // number of goals conceded by team 1.

        // team 1
        scores
            .entry(team_1_name)
            .and_modify(|score| {
                score.goals_scored += team_1_score;
                score.goals_conceded += team_2_score
            })
            .or_insert(TeamScores {
                goals_scored: team_1_score,
                goals_conceded: team_2_score,
            });

        // team 2
        scores
            .entry(team_2_name)
            .and_modify(|score| {
                score.goals_scored += team_2_score;
                score.goals_conceded += team_1_score
            })
            .or_insert(TeamScores {
                goals_scored: team_2_score,
                goals_conceded: team_1_score,
            });
    }

    scores
}

fn main() {
    // You can optionally experiment here.
}

#[cfg(test)]
mod tests {
    use super::*;

    const RESULTS: &str = "England,France,4,2
France,Italy,3,1
Poland,Spain,2,0
Germany,England,2,1
England,Spain,1,0";

    #[test]
    fn build_scores() {
        let scores = build_scores_table(RESULTS);

        assert!(["England", "France", "Germany", "Italy", "Poland", "Spain"]
            .into_iter()
            .all(|team_name| scores.contains_key(team_name)));
    }

    #[test]
    fn validate_team_score_1() {
        let scores = build_scores_table(RESULTS);
        let team = scores.get("England").unwrap();
        assert_eq!(team.goals_scored, 6);
        assert_eq!(team.goals_conceded, 4);
    }

    #[test]
    fn validate_team_score_2() {
        let scores = build_scores_table(RESULTS);
        let team = scores.get("Spain").unwrap();
        assert_eq!(team.goals_scored, 0);
        assert_eq!(team.goals_conceded, 3);
    }
}
```

* In this example we need to build a score table that contains:
  * Team Name: `Key`
  * Goals Scored: `TeamScores.goals_scored`
  * Goals Conceded `TeamScores.goals_conceded`
* There are multiple ways to do this and mostly we will use `Entry` because its more convenient.
* First we can use `and_modify` with `or_insert` like this:

  ```rust
  // team 1
  scores
      .entry(team_1_name)
      .and_modify(| score | {
          score.goals_scored += team_1_score;
          score.goals_conceded += team_2_score
      })
      .or_insert(TeamScores {
          goals_scored: team_1_score,
          goals_conceded: team_2_score,
      });

  // team 2
  scores
      .entry(team_2_name)
      .and_modify(| score | {
          score.goals_scored += team_2_score;
          score.goals_conceded += team_1_score
      })
      .or_insert(TeamScores {
          goals_scored: team_2_score,
          goals_conceded: team_1_score,
      }); 
  ```
* Or by using `or_default` and then adding the score like this:

  ```rust
  // Insert the default with zeros if a team doesn't exist yet.
  let team_1 = scores.entry(team_1_name).or_default();
  // Update the values.
  team_1.goals_scored += team_1_score;
  team_1.goals_conceded += team_2_score;

  // Similarly for the second team.
  let team_2 = scores.entry(team_2_name).or_default();
  team_2.goals_scored += team_2_score;
  team_2.goals_conceded += team_1_score;
  ```
* Which one do you think is better?


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://bagus-cahyono.gitbook.io/programming-notes/rust/rustlings_exercise/11_hashmaps.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
