{"id":"grouping-list-values","slug":"list-as-value","title":"A list as a value","text":"In module 31, each key of a hash map had one value, and a `put` with a key that was already there replaced the old value. But often one key has many things: a school club has many members, an album has many songs, a table in a restaurant orders many dishes. For those, the value can be a list. This module groups data that way, and then ends part 8 with a look at why a hash map finds its keys so fast.\n\n### A map whose values are lists\n\nA school keeps track of its clubs and their members. The club's name is the key, and the value is the list of its members:\n\n```java\nHashMap<String, ArrayList<String>> clubs = new HashMap<>();\nclubs.put(\"Debate\", new ArrayList<>());\nclubs.put(\"Robotics\", new ArrayList<>());\n\nclubs.get(\"Debate\").add(\"Amira\");\nclubs.get(\"Debate\").add(\"Jonas\");\nclubs.get(\"Robotics\").add(\"Priya\");\n\nSystem.out.println(clubs.get(\"Debate\"));\nSystem.out.println(clubs.get(\"Robotics\").size());\n```\n\n```output\n[Amira, Jonas]\n1\n```\n\n- The type tells the whole story: `HashMap<String, ArrayList<String>>` maps a String (the club) to a list of Strings (its members). The diamond in `new HashMap<>()` fills in both types.\n- `clubs.put(\"Debate\", new ArrayList<>())` puts a new, empty list in the map under the key `\"Debate\"`.\n- `clubs.get(\"Debate\")` gives back that list itself, not a copy: a reference to the same list object that's in the map (module 22). So `clubs.get(\"Debate\").add(\"Amira\")` adds Amira to the list in the map, and there's no need to put the list back.\n\n### A club's first member\n\nBefore a club gets its first member, the map has no list for it. `get` returns `null` for a key that isn't in the map, and `null` has no methods:\n\n```java\nimport java.util.ArrayList;\nimport java.util.HashMap;\n\npublic class Main {\n    public static void main(String[] args) {\n        HashMap<String, ArrayList<String>> clubs = new HashMap<>();\n        clubs.get(\"Drama\").add(\"Leo\");\n    }\n}\n```\n\n```crash\nException in thread \"main\" java.lang.NullPointerException: Cannot invoke \"java.util.ArrayList.add(Object)\" because the return value of \"java.util.HashMap.get(Object)\" is null\n\tat Main.main(Main.java:7)\n```\n\nSo adding takes two parts: create the list the first time the key is seen, and then add to the list that's in the map. As a method:\n\n```java\nimport java.util.ArrayList;\nimport java.util.HashMap;\n\npublic class Main {\n    public static void main(String[] args) {\n        HashMap<String, ArrayList<String>> clubs = new HashMap<>();\n        join(clubs, \"Debate\", \"Amira\");\n        join(clubs, \"Drama\", \"Leo\");\n        join(clubs, \"Debate\", \"Jonas\");\n        join(clubs, \"Drama\", \"Sofia\");\n        join(clubs, \"Debate\", \"Mei\");\n        System.out.println(clubs.get(\"Debate\"));\n        System.out.println(clubs.get(\"Drama\"));\n    }\n\n    public static void join(HashMap<String, ArrayList<String>> clubs, String club, String name) {\n        if (!clubs.containsKey(club)) {\n            clubs.put(club, new ArrayList<>());\n        }\n        clubs.get(club).add(name);\n    }\n}\n```\n\n```output\n[Amira, Jonas, Mei]\n[Leo, Sofia]\n```\n\n- The `if` runs only for a club's first member, and puts a new, empty list in the map for that club. Each club gets a list of its own.\n- After the `if`, the club always has a list, so `clubs.get(club).add(name)` is safe.\n- Each list is an ordinary ArrayList, so the members stay in the order they joined.\n- `join` gets a reference to the caller's map, so the lists it adds and fills are in the caller's map too.\n\nThe same can be written with `getOrDefault(key, other)`, which returns the key's value, or `other` when the key isn't in the map:\n\n```java\nArrayList<String> members = clubs.getOrDefault(club, new ArrayList<>());\nmembers.add(name);\nclubs.put(club, members);\n```\n\nHere the last line matters. For a new club, the new list is only in the variable `members` until `put` stores it in the map. Without the `put`, the club's first member would be lost. For a club that already has a list, `put` stores the same list again, which changes nothing. Both ways work. For adding, this module uses the one with `containsKey`.\n\n### Getting a group back\n\nA method that gives a club's members returns the club's list. For a club nobody has joined, it should return an **empty list**, not `null`:\n\n```java\nimport java.util.ArrayList;\nimport java.util.HashMap;\n\npublic class Main {\n    public static void main(String[] args) {\n        HashMap<String, ArrayList<String>> clubs = new HashMap<>();\n        join(clubs, \"Debate\", \"Amira\");\n        join(clubs, \"Debate\", \"Jonas\");\n        for (String name : membersOf(clubs, \"Debate\")) {\n            System.out.println(\"Debate: \" + name);\n        }\n        for (String name : membersOf(clubs, \"Astronomy\")) {\n            System.out.println(\"Astronomy: \" + name);\n        }\n        System.out.println(membersOf(clubs, \"Astronomy\").size() + \" in Astronomy\");\n    }\n\n    public static void join(HashMap<String, ArrayList<String>> clubs, String club, String name) {\n        if (!clubs.containsKey(club)) {\n            clubs.put(club, new ArrayList<>());\n        }\n        clubs.get(club).add(name);\n    }\n\n    public static ArrayList<String> membersOf(HashMap<String, ArrayList<String>> clubs, String club) {\n        return clubs.getOrDefault(club, new ArrayList<>());\n    }\n}\n```\n\n```output\nDebate: Amira\nDebate: Jonas\n0 in Astronomy\n```\n\n- With an empty list, the code that asks needs no special case: the for-each loop over Astronomy runs zero times, and `size()` is 0. With `null`, both would crash with a NullPointerException, so every caller would have to check for `null` first.\n- The empty list for Astronomy is a new list that isn't in the map. That's fine for reading. To add a member, use `join`.\n","task":"A community garden keeps a list of the plants in each bed. Fill in the blanks of `plant(beds, bed, plant)`, which adds a plant to the bed's list, and creates the list for the bed's first plant. The check runs:\n\n```java\nHashMap<String, ArrayList<String>> beds = new HashMap<>();\nplant(beds, \"North\", \"kale\");\nplant(beds, \"South\", \"beans\");\nplant(beds, \"North\", \"leeks\");\nSystem.out.println(beds.get(\"North\"));\nSystem.out.println(beds.get(\"South\"));\n```\n\nand expects:\n\n```\n[kale, leeks]\n[beans]\n```\n","kind":"fill","seed":"import java.util.ArrayList;\nimport java.util.HashMap;\n\npublic class Main {\n    public static void main(String[] args) {\n        HashMap<String, ArrayList<String>> beds = new HashMap<>();\n        plant(beds, \"North\", \"kale\");\n        plant(beds, \"South\", \"beans\");\n        plant(beds, \"North\", \"leeks\");\n        System.out.println(beds.get(\"North\"));\n        System.out.println(beds.get(\"South\"));\n    }\n\n    public static void plant(HashMap<String, ArrayList<String>> beds, String bed, String plant) {\n        if (!beds.[[containsKey]](bed)) {\n            beds.put(bed, [[new ArrayList<>()‖new ArrayList<String>()]]);\n        }\n        beds.[[get]](bed).add(plant);\n    }\n}\n","solution":"import java.util.ArrayList;\nimport java.util.HashMap;\n\npublic class Main {\n    public static void main(String[] args) {\n        HashMap<String, ArrayList<String>> beds = new HashMap<>();\n        plant(beds, \"North\", \"kale\");\n        plant(beds, \"South\", \"beans\");\n        plant(beds, \"North\", \"leeks\");\n        System.out.println(beds.get(\"North\"));\n        System.out.println(beds.get(\"South\"));\n    }\n\n    public static void plant(HashMap<String, ArrayList<String>> beds, String bed, String plant) {\n        if (!beds.containsKey(bed)) {\n            beds.put(bed, new ArrayList<>());\n        }\n        beds.get(bed).add(plant);\n    }\n}\n","hints":["It works like `join` above. The `if` asks whether the map already has the bed as a key, and the `!` makes it run only when it doesn't.","The bed's first plant needs a new, empty list: `new ArrayList<>()`. After the `if`, `beds.get(bed)` is the bed's list in the map, and `add` puts the plant on it."],"tests":[{"name":"Test 1","stdin":"","call":"HashMap<String, ArrayList<String>> beds = new HashMap<>();\nplant(beds, \"North\", \"kale\");\nplant(beds, \"South\", \"beans\");\nplant(beds, \"North\", \"leeks\");\nSystem.out.println(beds.get(\"North\"));\nSystem.out.println(beds.get(\"South\"));\n","expect":"[kale, leeks]\n[beans]","hidden":false},{"name":"Test 2","stdin":"","call":"HashMap<String, ArrayList<String>> beds = new HashMap<>();\nplant(beds, \"East\", \"chard\");\nplant(beds, \"East\", \"chard\");\nplant(beds, \"West\", \"garlic\");\nplant(beds, \"Herb\", \"mint\");\nplant(beds, \"East\", \"peas\");\nSystem.out.println(beds.get(\"East\"));\nSystem.out.println(beds.get(\"West\") + \" \" + beds.get(\"Herb\") + \" \" + beds.size());\n","expect":"[chard, chard, peas]\n[garlic] [mint] 3","hidden":true},{"name":"Test 3","stdin":"","call":"HashMap<String, ArrayList<String>> beds = new HashMap<>();\nplant(beds, \"Pond\", \"iris\");\nSystem.out.println(beds.get(\"Pond\") + \" \" + beds.get(\"Path\"));\n","expect":"[iris] null","hidden":true}],"require":[],"forbid":[],"more":[{"task":"What does this program print? Keep track of which variables refer to which list.\n","kind":"predict","seed":"import java.util.ArrayList;\nimport java.util.HashMap;\n\npublic class Main {\n    public static void main(String[] args) {\n        HashMap<String, ArrayList<String>> clubs = new HashMap<>();\n        clubs.put(\"Debate\", new ArrayList<>());\n        ArrayList<String> debate = clubs.get(\"Debate\");\n        debate.add(\"Amira\");\n        clubs.get(\"Debate\").add(\"Jonas\");\n        System.out.println(debate.size());\n\n        ArrayList<String> astronomy = clubs.getOrDefault(\"Astronomy\", new ArrayList<>());\n        astronomy.add(\"Leo\");\n        System.out.println(astronomy);\n        System.out.println(clubs.containsKey(\"Astronomy\"));\n        System.out.println(clubs.get(\"Debate\"));\n    }\n}\n","solution":"import java.util.ArrayList;\nimport java.util.HashMap;\n\npublic class Main {\n    public static void main(String[] args) {\n        HashMap<String, ArrayList<String>> clubs = new HashMap<>();\n        clubs.put(\"Debate\", new ArrayList<>());\n        ArrayList<String> debate = clubs.get(\"Debate\");\n        debate.add(\"Amira\");\n        clubs.get(\"Debate\").add(\"Jonas\");\n        System.out.println(debate.size());\n\n        ArrayList<String> astronomy = clubs.getOrDefault(\"Astronomy\", new ArrayList<>());\n        astronomy.add(\"Leo\");\n        System.out.println(astronomy);\n        System.out.println(clubs.containsKey(\"Astronomy\"));\n        System.out.println(clubs.get(\"Debate\"));\n    }\n}\n","hints":["`debate` and `clubs.get(\"Debate\")` refer to the same list object, the one in the map. Two names were added to it, one through each.","There's no Astronomy club in the map, so `getOrDefault` returns the new list it was given. Leo goes on that list, but nothing ever puts it in the map."],"tests":[{"name":"Output","stdin":"","expect":"2\n[Leo]\nfalse\n[Amira, Jonas]","hidden":false}],"require":[],"forbid":[],"lines":["2","[Leo]","false","[Amira, Jonas]"]},{"task":"A music app keeps the songs of each album. Write two methods:\n\n- `addSong(HashMap<String, ArrayList<String>> albums, String album, String song)` adds the song to the album's list, and creates the list for the album's first song.\n- `songsOn(HashMap<String, ArrayList<String>> albums, String album)` returns the album's list of songs, or an empty list for an album that has none.\n\nThe check runs:\n\n```java\nHashMap<String, ArrayList<String>> albums = new HashMap<>();\naddSong(albums, \"Long Weekend\", \"Porch Swing\");\naddSong(albums, \"Green Signal\", \"Crosswalk\");\naddSong(albums, \"Long Weekend\", \"Cloud Shapes\");\nSystem.out.println(songsOn(albums, \"Long Weekend\"));\nSystem.out.println(songsOn(albums, \"Winter Tapes\").size());\n```\n\nand expects:\n\n```\n[Porch Swing, Cloud Shapes]\n0\n```\n","kind":"code","seed":"import java.util.ArrayList;\nimport java.util.HashMap;\n\npublic class Main {\n    public static void main(String[] args) {\n        HashMap<String, ArrayList<String>> albums = new HashMap<>();\n        addSong(albums, \"Long Weekend\", \"Porch Swing\");\n        addSong(albums, \"Green Signal\", \"Crosswalk\");\n        addSong(albums, \"Long Weekend\", \"Cloud Shapes\");\n        System.out.println(songsOn(albums, \"Long Weekend\"));\n        System.out.println(songsOn(albums, \"Winter Tapes\").size());\n    }\n\n    // Write the methods addSong and songsOn here.\n}\n","solution":"import java.util.ArrayList;\nimport java.util.HashMap;\n\npublic class Main {\n    public static void main(String[] args) {\n        HashMap<String, ArrayList<String>> albums = new HashMap<>();\n        addSong(albums, \"Long Weekend\", \"Porch Swing\");\n        addSong(albums, \"Green Signal\", \"Crosswalk\");\n        addSong(albums, \"Long Weekend\", \"Cloud Shapes\");\n        System.out.println(songsOn(albums, \"Long Weekend\"));\n        System.out.println(songsOn(albums, \"Winter Tapes\").size());\n    }\n\n    public static void addSong(HashMap<String, ArrayList<String>> albums, String album, String song) {\n        if (!albums.containsKey(album)) {\n            albums.put(album, new ArrayList<>());\n        }\n        albums.get(album).add(song);\n    }\n\n    public static ArrayList<String> songsOn(HashMap<String, ArrayList<String>> albums, String album) {\n        return albums.getOrDefault(album, new ArrayList<>());\n    }\n}\n","hints":["`addSong` is the `join` of this step with new names: create the album's list when `containsKey` says it isn't there, and then add the song to `albums.get(album)`.","`songsOn` can be one line: `return albums.getOrDefault(album, new ArrayList<>());`. For an album with no songs, `get` would return `null`, and the check's `size()` would crash."],"tests":[{"name":"Test 1","stdin":"","call":"HashMap<String, ArrayList<String>> albums = new HashMap<>();\naddSong(albums, \"Long Weekend\", \"Porch Swing\");\naddSong(albums, \"Green Signal\", \"Crosswalk\");\naddSong(albums, \"Long Weekend\", \"Cloud Shapes\");\nSystem.out.println(songsOn(albums, \"Long Weekend\"));\nSystem.out.println(songsOn(albums, \"Winter Tapes\").size());\n","expect":"[Porch Swing, Cloud Shapes]\n0","hidden":false},{"name":"Test 2","stdin":"","call":"HashMap<String, ArrayList<String>> albums = new HashMap<>();\naddSong(albums, \"Tides\", \"Low\");\naddSong(albums, \"Sparks\", \"Flint\");\naddSong(albums, \"Tides\", \"High\");\naddSong(albums, \"Tides\", \"Low\");\nSystem.out.println(songsOn(albums, \"Tides\"));\nSystem.out.println(songsOn(albums, \"Sparks\").size() + \" \" + albums.size());\nSystem.out.println(albums.get(\"Tides\").size());\n","expect":"[Low, High, Low]\n1 2\n3","hidden":true},{"name":"Test 3","stdin":"","call":"HashMap<String, ArrayList<String>> albums = new HashMap<>();\nSystem.out.println(songsOn(albums, \"Anything\"));\nfor (String song : songsOn(albums, \"Nothing\")) {\n    System.out.println(song);\n}\naddSong(albums, \"Solo\", \"One\");\nSystem.out.println(songsOn(albums, \"Solo\") + \" \" + albums.size());\n","expect":"[]\n[One] 1","hidden":true}],"require":[{"pattern":"\\bpublic\\s+static\\s+void\\s+addSong\\s*\\(\\s*HashMap\\s*<\\s*String\\s*,\\s*ArrayList\\s*<\\s*String\\s*>\\s*>\\s+\\w+\\s*,\\s*String\\s+\\w+\\s*,\\s*String\\s+\\w+\\s*\\)","message":"Write the first method as `public static void addSong(HashMap<String, ArrayList<String>> albums, String album, String song)`."},{"pattern":"\\bpublic\\s+static\\s+ArrayList\\s*<\\s*String\\s*>\\s+songsOn\\s*\\(\\s*HashMap\\s*<\\s*String\\s*,\\s*ArrayList\\s*<\\s*String\\s*>\\s*>\\s+\\w+\\s*,\\s*String\\s+\\w+\\s*\\)","message":"Write the second method as `public static ArrayList<String> songsOn(HashMap<String, ArrayList<String>> albums, String album)`."}],"forbid":[]}]}