{"id":"hash-maps-one-value-per-key","slug":"one-value-per-key","title":"One value per key","text":"A key is linked to **at most one value**. Putting a key that's already in the map doesn't add a second pair: the new value takes the place of the old one.\n\nThe front desk of a small hotel keeps the name of the guest in each room, with the room numbers as keys. Keys can be numbers too, and like values, they're `Integer`, not `int`. `put(204, \"Okafor\")` turns the 204 into an Integer by itself.\n\n```java\nHashMap<Integer, String> guests = new HashMap<>();\nguests.put(204, \"Okafor\");\nguests.put(310, \"Lindqvist\");\nSystem.out.println(guests.get(204) + \", \" + guests.size() + \" rooms taken\");\n\nguests.put(204, \"Tanaka\");\nSystem.out.println(guests.get(204) + \", \" + guests.size() + \" rooms taken\");\n```\n\n```output\nOkafor, 2 rooms taken\nTanaka, 2 rooms taken\n```\n\n- `size()` is the number of keys in the map, which is also the number of pairs.\n- The second `put(204, ...)` replaced Okafor with Tanaka. The map still has two pairs, and Okafor isn't in it anymore.\n- Values have no such rule. Two rooms can have guests with the same name, since the rooms are different keys.\n\n(When a key needs several values, such as all the guests of a room, the value can be a list. Module 33 does that.)\n\n### Checking and removing\n\n| Method | What it does |\n| --- | --- |\n| `containsKey(key)` | `true` if the key is in the map, otherwise `false` |\n| `remove(key)` | removes the key and its value; a key that isn't there changes nothing |\n| `size()` | the number of keys |\n| `isEmpty()` | `true` when the map has no keys at all |\n\n```java\nHashMap<Integer, String> guests = new HashMap<>();\nguests.put(204, \"Tanaka\");\nguests.put(310, \"Lindqvist\");\n\nSystem.out.println(guests.containsKey(310));\nguests.remove(310);\nSystem.out.println(guests.containsKey(310) + \" \" + guests.get(310));\nguests.remove(512);\nSystem.out.println(guests.size() + \" \" + guests.isEmpty());\nguests.remove(204);\nSystem.out.println(guests.size() + \" \" + guests.isEmpty());\n```\n\n```output\ntrue\nfalse null\n1 false\n0 true\n```\n\nSince `put` replaces, a program that must not lose a value checks first. Here a new guest gets a room only if nobody is in it:\n\n```java\nif (guests.containsKey(room)) {\n    System.out.println(\"Room \" + room + \" is taken\");\n} else {\n    guests.put(room, guest);\n}\n```\n\n### Keys must match exactly\n\n`get`, `containsKey` and `remove` find a key only if it's equal to a key in the map. The map works out the key's hash code, looks only at the keys with that same hash code, and uses `equals` to tell whether one of them is equal. (Module 32 shows why both of those matter.) For Strings, equal means that every character is the same: `\"lis\"` and `\"LIS \"` are different keys from `\"LIS\"`. Keys that people type often come with small letters or extra spaces, so tidy them into the map's form first:\n\n```java\nimport java.util.HashMap;\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        HashMap<String, String> airports = new HashMap<>();\n        airports.put(\"HEL\", \"Helsinki\");\n        airports.put(\"LIS\", \"Lisbon\");\n\n        String typed = scanner.nextLine();\n        System.out.println(\"As typed: \" + airports.get(typed));\n        String code = typed.trim().toUpperCase();\n        System.out.println(\"Tidied: \" + airports.get(code));\n    }\n}\n```\n\n```input\n  lis\n```\n\n```output\nAs typed: null\nTidied: Lisbon\n```\n\n- `trim()` removes the spaces at the start and the end, and `toUpperCase()` turns the letters into capitals (module 16). The map's codes are in capitals, so the tidied code matches.\n- Choose one form for the keys, such as capitals for codes or small letters for words and titles, and tidy every key into it: when you `put` it, and when you look it up.\n","task":"What does this program print? One room is put twice, and one guest's name is in the map twice.\n","kind":"predict","seed":"import java.util.HashMap;\n\npublic class Main {\n    public static void main(String[] args) {\n        HashMap<Integer, String> guests = new HashMap<>();\n        guests.put(101, \"Moreau\");\n        guests.put(102, \"Achebe\");\n        guests.put(101, \"Kowalski\");\n        guests.put(103, \"Achebe\");\n        System.out.println(guests.size());\n        System.out.println(guests.get(101));\n        guests.remove(102);\n        guests.remove(104);\n        System.out.println(guests.containsKey(102) + \" \" + guests.size());\n        System.out.println(guests.get(103));\n    }\n}\n","solution":"import java.util.HashMap;\n\npublic class Main {\n    public static void main(String[] args) {\n        HashMap<Integer, String> guests = new HashMap<>();\n        guests.put(101, \"Moreau\");\n        guests.put(102, \"Achebe\");\n        guests.put(101, \"Kowalski\");\n        guests.put(103, \"Achebe\");\n        System.out.println(guests.size());\n        System.out.println(guests.get(101));\n        guests.remove(102);\n        guests.remove(104);\n        System.out.println(guests.containsKey(102) + \" \" + guests.size());\n        System.out.println(guests.get(103));\n    }\n}\n","hints":["Room 101 is put twice. A key has at most one value, so the second `put` replaces Moreau with Kowalski instead of adding a pair. Achebe in two rooms is fine: the rooms are different keys.","Removing 102 takes that pair out. The map has no room 104, so removing it changes nothing."],"tests":[{"name":"Output","stdin":"","expect":"3\nKowalski\nfalse 2\nAchebe","hidden":false}],"require":[],"forbid":[],"lines":["3","Kowalski","false 2","Achebe"],"more":[{"task":"A pantry keeps its jars in a `HashMap<String, Integer>`: each food, and how many jars of it there are. A food is in the map only while there's at least one jar of it. Write the method `useOne(HashMap<String, Integer> jars, String food)`. If the pantry has the food, the method takes one jar away and returns `true`, and when that was the last jar, it removes the food from the map. If the pantry has no such food, the method changes nothing and returns `false`. The check runs:\n\n```java\nHashMap<String, Integer> jars = new HashMap<>();\njars.put(\"marmalade\", 2);\njars.put(\"olives\", 1);\nSystem.out.println(useOne(jars, \"marmalade\") + \" \" + jars.get(\"marmalade\"));\nSystem.out.println(useOne(jars, \"olives\") + \" \" + jars.containsKey(\"olives\"));\nSystem.out.println(useOne(jars, \"olives\") + \" \" + jars.size());\n```\n\nand expects:\n\n```\ntrue 1\ntrue false\nfalse 1\n```\n","kind":"code","seed":"import java.util.HashMap;\n\npublic class Main {\n    public static void main(String[] args) {\n        HashMap<String, Integer> jars = new HashMap<>();\n        jars.put(\"marmalade\", 2);\n        jars.put(\"olives\", 1);\n        System.out.println(useOne(jars, \"marmalade\") + \" \" + jars.get(\"marmalade\"));\n        System.out.println(useOne(jars, \"olives\") + \" \" + jars.containsKey(\"olives\"));\n        System.out.println(useOne(jars, \"olives\") + \" \" + jars.size());\n    }\n\n    // Write the method useOne here.\n}\n","solution":"import java.util.HashMap;\n\npublic class Main {\n    public static void main(String[] args) {\n        HashMap<String, Integer> jars = new HashMap<>();\n        jars.put(\"marmalade\", 2);\n        jars.put(\"olives\", 1);\n        System.out.println(useOne(jars, \"marmalade\") + \" \" + jars.get(\"marmalade\"));\n        System.out.println(useOne(jars, \"olives\") + \" \" + jars.containsKey(\"olives\"));\n        System.out.println(useOne(jars, \"olives\") + \" \" + jars.size());\n    }\n\n    public static boolean useOne(HashMap<String, Integer> jars, String food) {\n        if (!jars.containsKey(food)) {\n            return false;\n        }\n        int left = jars.get(food) - 1;\n        if (left == 0) {\n            jars.remove(food);\n        } else {\n            jars.put(food, left);\n        }\n        return true;\n    }\n}\n","hints":["Start with `if (!jars.containsKey(food)) { return false; }`. After that, the food is surely in the map, so `jars.get(food)` is a number, not `null`.","Work out the jars that are left, `jars.get(food) - 1`. When that's 0, `remove` the food; otherwise `put` the new count, which replaces the old one."],"tests":[{"name":"Test 1","stdin":"","call":"HashMap<String, Integer> jars = new HashMap<>();\njars.put(\"marmalade\", 2);\njars.put(\"olives\", 1);\nSystem.out.println(useOne(jars, \"marmalade\") + \" \" + jars.get(\"marmalade\"));\nSystem.out.println(useOne(jars, \"olives\") + \" \" + jars.containsKey(\"olives\"));\nSystem.out.println(useOne(jars, \"olives\") + \" \" + jars.size());\n","expect":"true 1\ntrue false\nfalse 1","hidden":false},{"name":"Test 2","stdin":"","call":"HashMap<String, Integer> jars = new HashMap<>();\njars.put(\"pickles\", 3);\njars.put(\"mustard\", 1);\nSystem.out.println(useOne(jars, \"pickles\") + \" \" + useOne(jars, \"pickles\") + \" \" + jars.get(\"pickles\"));\nSystem.out.println(useOne(jars, \"Pickles\") + \" \" + jars.size());\nSystem.out.println(useOne(jars, \"mustard\") + \" \" + useOne(jars, \"mustard\") + \" \" + jars.containsKey(\"mustard\"));\nSystem.out.println(useOne(jars, \"pickles\") + \" \" + jars.isEmpty());\n","expect":"true true 1\nfalse 2\ntrue false false\ntrue true","hidden":true},{"name":"Test 3","stdin":"","call":"HashMap<String, Integer> jars = new HashMap<>();\nSystem.out.println(useOne(jars, \"salsa\") + \" \" + jars.containsKey(\"salsa\") + \" \" + jars.size());\njars.put(\"salsa\", 10);\nSystem.out.println(useOne(jars, \"salsa\") + \" \" + jars.get(\"salsa\"));\n","expect":"false false 0\ntrue 9","hidden":true}],"require":[{"pattern":"\\bpublic\\s+static\\s+boolean\\s+useOne\\s*\\(\\s*HashMap<String,\\s*Integer>\\s+\\w+\\s*,\\s*String\\s+\\w+\\s*\\)","message":"Write the method as `public static boolean useOne(HashMap<String, Integer> jars, String food)`."},{"pattern":"\\.remove\\s*\\(","message":"Take a food out of the map with `remove` when its last jar is used."}],"forbid":[]},{"task":"A radio station takes song requests by text message, and people type the titles any way they like. The map `catalog` has every song the station can play, with the title in small letters as the key and the artist as the value. Read requests, one per line, until an empty line. Tidy each title with `trim()` and `toLowerCase()`, and then print either `Playing `, the tidied title, ` by ` and the artist, or `Not in the catalog: ` and the tidied title. For the input shown, the program prints:\n\n```\nPlaying night bus by Mira Vale\nPlaying paper lanterns by The Quiet Hours\nNot in the catalog: blue tuesday\n```\n","kind":"code","seed":"import java.util.HashMap;\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        HashMap<String, String> catalog = new HashMap<>();\n        catalog.put(\"paper lanterns\", \"The Quiet Hours\");\n        catalog.put(\"night bus\", \"Mira Vale\");\n        catalog.put(\"cedar smoke\", \"Juniper Road\");\n        catalog.put(\"slow river\", \"The Quiet Hours\");\n\n        // Read the requests here.\n    }\n}\n","solution":"import java.util.HashMap;\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        HashMap<String, String> catalog = new HashMap<>();\n        catalog.put(\"paper lanterns\", \"The Quiet Hours\");\n        catalog.put(\"night bus\", \"Mira Vale\");\n        catalog.put(\"cedar smoke\", \"Juniper Road\");\n        catalog.put(\"slow river\", \"The Quiet Hours\");\n\n        while (true) {\n            String line = scanner.nextLine();\n            if (line.isEmpty()) {\n                break;\n            }\n            String title = line.trim().toLowerCase();\n            String artist = catalog.get(title);\n            if (artist == null) {\n                System.out.println(\"Not in the catalog: \" + title);\n            } else {\n                System.out.println(\"Playing \" + title + \" by \" + artist);\n            }\n        }\n    }\n}\n","hints":["Read in a `while (true)` loop that stops with `break` at the empty line. Tidy each line with `line.trim().toLowerCase()` before looking it up.","`catalog.get(title)` gives the artist, or `null` for a title the station doesn't have."],"tests":[{"name":"Test 1","stdin":"Night Bus\n  paper LANTERNS\nBlue Tuesday\n\n","expect":"Playing night bus by Mira Vale\nPlaying paper lanterns by The Quiet Hours\nNot in the catalog: blue tuesday","hidden":false},{"name":"Test 2","stdin":"CEDAR SMOKE   \nslow river\nMira Vale\n\n","expect":"Playing cedar smoke by Juniper Road\nPlaying slow river by The Quiet Hours\nNot in the catalog: mira vale","hidden":true},{"name":"Test 3","stdin":"Night bus\nnight bus\n\nSlow River\n","expect":"Playing night bus by Mira Vale\nPlaying night bus by Mira Vale","hidden":true}],"require":[{"pattern":"\\.trim\\s*\\(","message":"Remove the extra spaces with `trim()`."},{"pattern":"\\.toLowerCase\\s*\\(","message":"Turn the title into small letters with `toLowerCase()`, the form of the map's keys."}],"forbid":[]}]}