{"id":"hash-maps-map-in-a-class","slug":"map-in-a-class","title":"A hash map inside a class","text":"A map can be an instance variable, like a list (module 24). The class creates the map in its constructor, and its methods use it. The rest of the program calls the methods, and doesn't need to know that there's a map inside.\n\nA radio station logs how many times each song was played:\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        PlayLog log = new PlayLog();\n        log.record(\"Night Bus\");\n        log.record(\"Paper Lanterns\");\n        log.record(\"Night Bus\");\n        System.out.println(log.timesPlayed(\"Night Bus\"));\n        System.out.println(log.timesPlayed(\"Paper Lanterns\"));\n        System.out.println(log.timesPlayed(\"Slow River\"));\n    }\n}\n// ==== PlayLog.java ====\nimport java.util.HashMap;\n\npublic class PlayLog {\n    private HashMap<String, Integer> plays;\n\n    public PlayLog() {\n        this.plays = new HashMap<>();\n    }\n\n    public void record(String title) {\n        int before = this.plays.getOrDefault(title, 0);\n        this.plays.put(title, before + 1);\n    }\n\n    public int timesPlayed(String title) {\n        return this.plays.getOrDefault(title, 0);\n    }\n}\n```\n\n```output\n2\n1\n0\n```\n\n- The constructor creates the map. Without `this.plays = new HashMap<>();`, the instance variable would be `null` (module 22), and the first `record` would crash with a NullPointerException.\n- `record` is the way to **count** with a map: get the count so far, with 0 for a song that hasn't been played yet, and put back one more. The `put` replaces the old count, since a key has only one value.\n- `timesPlayed` gives 0 for a song that was never played, not `null`, so the caller can use the answer as a number right away.\n\nThe count can also be written with `containsKey`. It does the same:\n\n```java\nif (this.plays.containsKey(title)) {\n    this.plays.put(title, this.plays.get(title) + 1);\n} else {\n    this.plays.put(title, 1);\n}\n```\n\n### Tidy the keys in one place\n\nWhen the keys come from people, the class can tidy every key itself. A private method keeps the tidying in one place, and every method that uses a key calls it, so a title is tidied the same way whether it's recorded or looked up:\n\n```java\npublic void record(String title) {\n    String key = tidy(title);\n    this.plays.put(key, this.plays.getOrDefault(key, 0) + 1);\n}\n\npublic int timesPlayed(String title) {\n    return this.plays.getOrDefault(tidy(title), 0);\n}\n\nprivate String tidy(String title) {\n    return title.trim().toLowerCase();\n}\n```\n\nNow `record(\"Night Bus\")` and `record(\" night bus\")` count the same song, and `timesPlayed(\"NIGHT BUS\")` finds it. A method that forgot to call `tidy` would look for another key, and find nothing.\n","task":"An airport's baggage storage keeps travelers' bags on numbered shelves. Each bag gets a tag such as `B-417`, and the staff type the tags in any form: ` b-417` must find the same bag. Write the class `BagStorage`, which keeps the shelf of each bag in a `HashMap<String, Integer>`, with the tags as keys. Tidy every tag with `trim()` and `toUpperCase()` before it's used as a key. A private method `tidy` keeps that in one place.\n\n- The constructor creates the map.\n- `leave(String tag, int shelf)` puts the bag on the shelf. When a bag with that tag is already stored, the staff have moved it: the method gives back the shelf it was on before. For a new bag, it gives back -1.\n- `collect(String tag)` is for a traveler who comes back for a bag. It gives back the shelf to fetch the bag from, and after that the bag isn't stored anymore. For a tag that isn't stored, it gives back -1.\n- `bagsStored()` returns how many bags are stored.\n\n`Main.java` is a test drive, and prints:\n\n```\n-1\n-1\n3\n2\n1 -1 1\n```\n","kind":"code","seed":"// ==== BagStorage.java ====\nimport java.util.HashMap;\n\npublic class BagStorage {\n    private HashMap<String, Integer> shelves;\n\n    public BagStorage() {\n        // Create the map here.\n    }\n\n    public int leave(String tag, int shelf) {\n        return 0;\n    }\n\n    public int collect(String tag) {\n        return 0;\n    }\n\n    public int bagsStored() {\n        return 0;\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        BagStorage storage = new BagStorage();\n        System.out.println(storage.leave(\"B-417\", 3));\n        System.out.println(storage.leave(\" c-802\", 5));\n        System.out.println(storage.leave(\"b-417 \", 1));\n        System.out.println(storage.bagsStored());\n        System.out.println(storage.collect(\"B-417\") + \" \" + storage.collect(\"b-417\") + \" \" + storage.bagsStored());\n    }\n}\n","solution":"// ==== BagStorage.java ====\nimport java.util.HashMap;\n\npublic class BagStorage {\n    private HashMap<String, Integer> shelves;\n\n    public BagStorage() {\n        this.shelves = new HashMap<>();\n    }\n\n    public int leave(String tag, int shelf) {\n        String key = tidy(tag);\n        int before = this.shelves.getOrDefault(key, -1);\n        this.shelves.put(key, shelf);\n        return before;\n    }\n\n    public int collect(String tag) {\n        String key = tidy(tag);\n        int shelf = this.shelves.getOrDefault(key, -1);\n        this.shelves.remove(key);\n        return shelf;\n    }\n\n    public int bagsStored() {\n        return this.shelves.size();\n    }\n\n    private String tidy(String tag) {\n        return tag.trim().toUpperCase();\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        BagStorage storage = new BagStorage();\n        System.out.println(storage.leave(\"B-417\", 3));\n        System.out.println(storage.leave(\" c-802\", 5));\n        System.out.println(storage.leave(\"b-417 \", 1));\n        System.out.println(storage.bagsStored());\n        System.out.println(storage.collect(\"B-417\") + \" \" + storage.collect(\"b-417\") + \" \" + storage.bagsStored());\n    }\n}\n","hints":["The constructor needs `this.shelves = new HashMap<>();`, or the map stays `null`. Then write `private String tidy(String tag) { return tag.trim().toUpperCase(); }`, and call it first in every method that gets a tag.","`leave` and `collect` both start by getting the shelf the map has for the tag, `getOrDefault(key, -1)`, which is -1 for a tag that isn't stored. Then `leave` puts the new shelf, which replaces an old one, and `collect` takes the tag out with `remove`. Both give back the shelf they got first."],"tests":[{"name":"The test drive","stdin":"","expect":"-1\n-1\n3\n2\n1 -1 1","hidden":false},{"name":"Test 2","stdin":"","call":"BagStorage s = new BagStorage();\nSystem.out.println(s.bagsStored() + \" \" + s.collect(\"X-1\"));\nSystem.out.println(s.leave(\"x-1\", 7) + \" \" + s.leave(\"X-2\", 7) + \" \" + s.leave(\" X-1\", 2) + \" \" + s.leave(\"X-1\", 4));\nSystem.out.println(s.bagsStored() + \" \" + s.collect(\"x-2 \") + \" \" + s.bagsStored());\nSystem.out.println(s.collect(\" x-1\") + \" \" + s.collect(\"X-1\") + \" \" + s.bagsStored() + \" \" + s.leave(\"X-2\", 6));\n","expect":"0 -1\n-1 -1 7 2\n2 7 1\n4 -1 0 -1","hidden":true},{"name":"Test 3","stdin":"","call":"BagStorage a = new BagStorage();\nBagStorage b = new BagStorage();\na.leave(\"K-9\", 0);\nSystem.out.println(b.collect(\"k-9\") + \" \" + b.bagsStored() + \" \" + a.bagsStored());\nSystem.out.println(a.leave(\"k-9 \", 12) + \" \" + a.collect(\"K-9\") + \" \" + a.bagsStored());\n","expect":"-1 0 1\n0 12 0","hidden":true}],"require":[{"pattern":"\\.trim\\s*\\(","message":"Remove the extra spaces of a tag with `trim()`."},{"pattern":"\\.toUpperCase\\s*\\(","message":"Turn a tag into capitals with `toUpperCase()`."},{"pattern":"\\b(getOrDefault|containsKey)\\s*\\(","message":"Find out whether a tag is stored with `getOrDefault` or `containsKey`."},{"pattern":"\\.put\\s*\\(","message":"Put a left bag's shelf in the map with `put`."},{"pattern":"\\.remove\\s*\\(","message":"Take a collected bag out of the map with `remove`."}],"forbid":[{"pattern":"\\bArrayList\\b","message":"Keep the tags and their shelves in the HashMap."}],"more":[{"task":"A radio station keeps a separate play log for its morning show and for its evening show, with the `PlayLog` class from above and one more method, `songs()`, which returns how many different songs the log has. What does this program print?\n","kind":"predict","seed":"public class Main {\n    public static void main(String[] args) {\n        PlayLog morning = new PlayLog();\n        PlayLog evening = new PlayLog();\n        morning.record(\"Night Bus\");\n        evening.record(\"Night Bus\");\n        morning.record(\"Night Bus\");\n        evening.record(\"Slow River\");\n        System.out.println(morning.timesPlayed(\"Night Bus\") + \" \" + evening.timesPlayed(\"Night Bus\"));\n        System.out.println(morning.timesPlayed(\"Slow River\") + \" \" + evening.timesPlayed(\"Slow River\"));\n        System.out.println(morning.songs() + \" \" + evening.songs());\n    }\n}\n// ==== PlayLog.java ====\nimport java.util.HashMap;\n\npublic class PlayLog {\n    private HashMap<String, Integer> plays;\n\n    public PlayLog() {\n        this.plays = new HashMap<>();\n    }\n\n    public void record(String title) {\n        int before = this.plays.getOrDefault(title, 0);\n        this.plays.put(title, before + 1);\n    }\n\n    public int timesPlayed(String title) {\n        return this.plays.getOrDefault(title, 0);\n    }\n\n    public int songs() {\n        return this.plays.size();\n    }\n}\n","solution":"public class Main {\n    public static void main(String[] args) {\n        PlayLog morning = new PlayLog();\n        PlayLog evening = new PlayLog();\n        morning.record(\"Night Bus\");\n        evening.record(\"Night Bus\");\n        morning.record(\"Night Bus\");\n        evening.record(\"Slow River\");\n        System.out.println(morning.timesPlayed(\"Night Bus\") + \" \" + evening.timesPlayed(\"Night Bus\"));\n        System.out.println(morning.timesPlayed(\"Slow River\") + \" \" + evening.timesPlayed(\"Slow River\"));\n        System.out.println(morning.songs() + \" \" + evening.songs());\n    }\n}\n// ==== PlayLog.java ====\nimport java.util.HashMap;\n\npublic class PlayLog {\n    private HashMap<String, Integer> plays;\n\n    public PlayLog() {\n        this.plays = new HashMap<>();\n    }\n\n    public void record(String title) {\n        int before = this.plays.getOrDefault(title, 0);\n        this.plays.put(title, before + 1);\n    }\n\n    public int timesPlayed(String title) {\n        return this.plays.getOrDefault(title, 0);\n    }\n\n    public int songs() {\n        return this.plays.size();\n    }\n}\n","hints":["Each `new PlayLog()` runs the constructor, which creates a map of its own. So the two logs have two separate maps, and a song recorded in one isn't in the other.","The morning log gets Night Bus twice, and the evening log gets Night Bus once and Slow River once. `songs()` is the size of the log's own map: the number of different titles, not of plays."],"tests":[{"name":"Output","stdin":"","expect":"2 1\n0 1\n1 2","hidden":false}],"require":[],"forbid":[],"lines":["2 1","0 1","1 2"]},{"task":"A museum's audio guide keeps two maps with the same keys, the stop numbers: one for the title of each stop, and one for how many times each stop has been played. Write the class `AudioGuide`:\n\n- The constructor creates both maps.\n- `addStop(int number, String title)` adds a stop.\n- `play(int number)` returns the stop's title and counts one more play of the stop. For a number with no stop, it returns `No stop ` and the number, and counts nothing.\n- `playsOf(int number)` returns how many times the stop has been played: 0 for a stop that hasn't been played yet, and for a number with no stop.\n\nThe test drive in `Main.java` prints:\n\n```\nBronze Horse\nBronze Horse\nNo stop 72\n2 0 0\n```\n","kind":"code","seed":"// ==== AudioGuide.java ====\nimport java.util.HashMap;\n\npublic class AudioGuide {\n    private HashMap<Integer, String> titles;\n    private HashMap<Integer, Integer> plays;\n\n    public AudioGuide() {\n        // Create the maps here.\n    }\n\n    public void addStop(int number, String title) {\n        // Replace the placeholders with the real code.\n    }\n\n    public String play(int number) {\n        return \"\";\n    }\n\n    public int playsOf(int number) {\n        return 0;\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        AudioGuide guide = new AudioGuide();\n        guide.addStop(12, \"The Harbor at Dawn\");\n        guide.addStop(27, \"Bronze Horse\");\n        System.out.println(guide.play(27));\n        System.out.println(guide.play(27));\n        System.out.println(guide.play(72));\n        System.out.println(guide.playsOf(27) + \" \" + guide.playsOf(12) + \" \" + guide.playsOf(72));\n    }\n}\n","solution":"// ==== AudioGuide.java ====\nimport java.util.HashMap;\n\npublic class AudioGuide {\n    private HashMap<Integer, String> titles;\n    private HashMap<Integer, Integer> plays;\n\n    public AudioGuide() {\n        this.titles = new HashMap<>();\n        this.plays = new HashMap<>();\n    }\n\n    public void addStop(int number, String title) {\n        this.titles.put(number, title);\n    }\n\n    public String play(int number) {\n        if (!this.titles.containsKey(number)) {\n            return \"No stop \" + number;\n        }\n        this.plays.put(number, this.plays.getOrDefault(number, 0) + 1);\n        return this.titles.get(number);\n    }\n\n    public int playsOf(int number) {\n        return this.plays.getOrDefault(number, 0);\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        AudioGuide guide = new AudioGuide();\n        guide.addStop(12, \"The Harbor at Dawn\");\n        guide.addStop(27, \"Bronze Horse\");\n        System.out.println(guide.play(27));\n        System.out.println(guide.play(27));\n        System.out.println(guide.play(72));\n        System.out.println(guide.playsOf(27) + \" \" + guide.playsOf(12) + \" \" + guide.playsOf(72));\n    }\n}\n","hints":["In the constructor, create both maps with `new HashMap<>()`. `addStop` puts the title in `this.titles`, under the number.","In `play`, check `this.titles.containsKey(number)` first, and return the message when the stop is missing. Otherwise count the play the way `record` does above, `this.plays.put(number, this.plays.getOrDefault(number, 0) + 1);`, and return the title. `playsOf` is a `getOrDefault` with 0."],"tests":[{"name":"The test drive","stdin":"","expect":"Bronze Horse\nBronze Horse\nNo stop 72\n2 0 0","hidden":false},{"name":"Test 2","stdin":"","call":"AudioGuide guide = new AudioGuide();\nguide.addStop(3, \"Glass Garden\");\nguide.addStop(8, \"The Iron Clock\");\nguide.addStop(15, \"Map of the Old Port\");\nSystem.out.println(guide.play(8) + \", \" + guide.play(3) + \", \" + guide.play(8) + \", \" + guide.play(8));\nSystem.out.println(guide.play(5) + \" \" + guide.play(5));\nSystem.out.println(guide.playsOf(8) + \" \" + guide.playsOf(3) + \" \" + guide.playsOf(15) + \" \" + guide.playsOf(5));\n","expect":"The Iron Clock, Glass Garden, The Iron Clock, The Iron Clock\nNo stop 5 No stop 5\n3 1 0 0","hidden":true},{"name":"Test 3","stdin":"","call":"AudioGuide guide = new AudioGuide();\nSystem.out.println(guide.play(1) + \" \" + guide.playsOf(1));\nguide.addStop(1, \"Welcome Hall\");\nSystem.out.println(guide.play(1) + \" \" + guide.playsOf(1));\nAudioGuide other = new AudioGuide();\nSystem.out.println(other.playsOf(1) + \" \" + other.play(1));\n","expect":"No stop 1 0\nWelcome Hall 1\n0 No stop 1","hidden":true}],"require":[],"forbid":[{"pattern":"\\bArrayList\\b","message":"Keep the titles and the plays in the two HashMaps."}]}]}