{"id":"grouping-in-a-class","slug":"groups-in-a-class","title":"Groups inside a class","text":"So far, the map of lists was passed to methods as a parameter. In a real program it usually lives inside a class, as an instance variable, the way module 24 kept a list inside an object. The class's methods are then the only way to change it. The rest of the program says what it wants done, like `register.join(\"Debate\", \"Amira\")`, and doesn't need to know that there's a map of lists inside.\n\nHere's a register of the school's clubs. `Main` is a test drive:\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        ClubRegister register = new ClubRegister();\n        register.join(\"Robotics\", \"Priya\");\n        register.join(\"Debate\", \"Amira\");\n        register.join(\"Debate\", \"Jonas\");\n        register.join(\"Art\", \"Leo\");\n        System.out.println(register.clubNames());\n        System.out.println(register.members(\"Debate\"));\n\n        register.leave(\"Debate\", \"Amira\");\n        register.leave(\"Art\", \"Leo\");\n        System.out.println(register.members(\"Debate\"));\n        System.out.println(register.clubNames());\n        System.out.println(register.members(\"Art\"));\n    }\n}\n// ==== ClubRegister.java ====\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\n\npublic class ClubRegister {\n    private HashMap<String, ArrayList<String>> clubs;\n\n    public ClubRegister() {\n        this.clubs = new HashMap<>();\n    }\n\n    public void join(String club, String student) {\n        if (!this.clubs.containsKey(club)) {\n            this.clubs.put(club, new ArrayList<>());\n        }\n        this.clubs.get(club).add(student);\n    }\n\n    public ArrayList<String> members(String club) {\n        return this.clubs.getOrDefault(club, new ArrayList<>());\n    }\n\n    public void leave(String club, String student) {\n        if (this.clubs.containsKey(club)) {\n            ArrayList<String> members = this.clubs.get(club);\n            members.remove(student);\n            if (members.isEmpty()) {\n                this.clubs.remove(club);\n            }\n        }\n    }\n\n    public ArrayList<String> clubNames() {\n        ArrayList<String> names = new ArrayList<>(this.clubs.keySet());\n        Collections.sort(names);\n        return names;\n    }\n}\n```\n\n```output\n[Art, Debate, Robotics]\n[Amira, Jonas]\n[Jonas]\n[Debate, Robotics]\n[]\n```\n\n- The constructor creates the map. Without that line, `this.clubs` would be `null`, and the first `join` would crash (module 22).\n- `join` and `members` are step 1's `join` and `membersOf`, with `this.clubs` in place of a parameter.\n- `leave` uses the list's `remove(student)`, which removes the first copy of the student and does nothing if the student isn't there (module 14). When the last member leaves, `this.clubs.remove(club)` takes the club out of the map too. Without that, a club with an empty list would stay in the map, and in `clubNames`, forever.\n- `clubNames` returns a new, sorted list of the keys. It's a list of its own: sorting it, or anything the caller does with it, doesn't change the map. The names come out in alphabetical order whatever order the map keeps its keys in.\n- ClubRegister prints nothing. Its methods return their answers, and `Main` decides what to print.\n\nOne thing to be aware of: `members` returns the list that's in the map, so code outside the class could change it, for example with `register.members(\"Debate\").clear()`. A class that wants to guard its data can return a copy instead: `new ArrayList<>(list)` makes a new list with the same elements as `list`.\n","task":"A restaurant keeps the dishes that each table has ordered. Tables have numbers, so the keys are `Integer`s: `HashMap<Integer, ArrayList<String>>`. In `TableOrders.java`, write:\n\n- the constructor, which creates the map,\n- `add(int table, String dish)`, which adds the dish to the table's list, and creates the list for the table's first dish,\n- `dishesAt(int table)`, which returns the table's list of dishes, or an empty list for a table with no orders,\n- `tables()`, which returns a new list of the table numbers that have orders, from the smallest to the largest.\n\n`Main.java` is a test drive, and it prints:\n\n```\n[3, 12, 21]\n[soup, salad]\n[]\n```\n","kind":"code","seed":"// ==== TableOrders.java ====\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\n\npublic class TableOrders {\n    private HashMap<Integer, ArrayList<String>> orders;\n\n    public TableOrders() {\n        // Create the map here.\n    }\n\n    public void add(int table, String dish) {\n\n    }\n\n    public ArrayList<String> dishesAt(int table) {\n        return null;\n    }\n\n    public ArrayList<Integer> tables() {\n        return null;\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        TableOrders orders = new TableOrders();\n        orders.add(12, \"soup\");\n        orders.add(3, \"curry\");\n        orders.add(12, \"salad\");\n        orders.add(21, \"tea\");\n        System.out.println(orders.tables());\n        System.out.println(orders.dishesAt(12));\n        System.out.println(orders.dishesAt(5));\n    }\n}\n","solution":"// ==== TableOrders.java ====\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\n\npublic class TableOrders {\n    private HashMap<Integer, ArrayList<String>> orders;\n\n    public TableOrders() {\n        this.orders = new HashMap<>();\n    }\n\n    public void add(int table, String dish) {\n        if (!this.orders.containsKey(table)) {\n            this.orders.put(table, new ArrayList<>());\n        }\n        this.orders.get(table).add(dish);\n    }\n\n    public ArrayList<String> dishesAt(int table) {\n        return this.orders.getOrDefault(table, new ArrayList<>());\n    }\n\n    public ArrayList<Integer> tables() {\n        ArrayList<Integer> tables = new ArrayList<>(this.orders.keySet());\n        Collections.sort(tables);\n        return tables;\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        TableOrders orders = new TableOrders();\n        orders.add(12, \"soup\");\n        orders.add(3, \"curry\");\n        orders.add(12, \"salad\");\n        orders.add(21, \"tea\");\n        System.out.println(orders.tables());\n        System.out.println(orders.dishesAt(12));\n        System.out.println(orders.dishesAt(5));\n    }\n}\n","hints":["The constructor is one line: `this.orders = new HashMap<>();`. `add` and `dishesAt` are ClubRegister's `join` and `members`, with an `int` table as the key. Java turns the `int` into an `Integer` key by itself.","For `tables()`, copy the keys into a new list, `new ArrayList<>(this.orders.keySet())`, sort it with `Collections.sort`, and return it. Integers sort by their value, so 3 comes before 12."],"tests":[{"name":"The test drive","stdin":"","expect":"[3, 12, 21]\n[soup, salad]\n[]","hidden":false},{"name":"Test 2","stdin":"","call":"TableOrders orders = new TableOrders();\nSystem.out.println(orders.tables() + \" \" + orders.dishesAt(1));\norders.add(17, \"tea\");\norders.add(5, \"soup\");\norders.add(30, \"cake\");\norders.add(16, \"pie\");\norders.add(5, \"tea\");\nSystem.out.println(orders.tables());\nSystem.out.println(orders.dishesAt(5) + \" \" + orders.dishesAt(17));\n","expect":"[] []\n[5, 16, 17, 30]\n[soup, tea] [tea]","hidden":true},{"name":"Test 3","stdin":"","call":"TableOrders orders = new TableOrders();\norders.add(2, \"soup\");\norders.add(2, \"soup\");\norders.add(40, \"salad\");\norders.add(8, \"juice\");\nSystem.out.println(orders.dishesAt(2).size() + \" \" + orders.tables());\nfor (String dish : orders.dishesAt(9)) {\n    System.out.println(dish);\n}\nSystem.out.println(orders.dishesAt(40));\n","expect":"2 [2, 8, 40]\n[salad]","hidden":true}],"require":[],"forbid":[{"pattern":"\\bclass\\s+TableOrders\\b[\\s\\S]*\\bSystem\\s*\\.\\s*out\\b[\\s\\S]*\\bclass\\s+Main\\b","message":"TableOrders doesn't print. Its methods return their answers, and `main` prints them."}],"more":[{"task":"A garden keeps the plants of each bed, and `plantsIn` returns the list that's in the map, as ClubRegister's `members` does. What does this program print?\n","kind":"predict","seed":"import java.util.ArrayList;\n\npublic class Main {\n    public static void main(String[] args) {\n        Garden garden = new Garden();\n        garden.plant(\"North\", \"kale\");\n        garden.plant(\"North\", \"leeks\");\n        garden.plant(\"Herb\", \"mint\");\n        garden.plant(\"Herb\", \"sage\");\n\n        ArrayList<String> north = garden.plantsIn(\"North\");\n        north.add(\"nettles\");\n        ArrayList<String> herbs = garden.plantsIn(\"Herb\");\n        garden.plantsIn(\"Herb\").clear();\n\n        System.out.println(garden.plantsIn(\"North\"));\n        System.out.println(herbs.size() + \" \" + garden.totalPlants());\n        System.out.println(garden.bedCount());\n    }\n}\n// ==== Garden.java ====\nimport java.util.ArrayList;\nimport java.util.HashMap;\n\npublic class Garden {\n    private HashMap<String, ArrayList<String>> beds;\n\n    public Garden() {\n        this.beds = new HashMap<>();\n    }\n\n    public void plant(String bed, String plant) {\n        if (!this.beds.containsKey(bed)) {\n            this.beds.put(bed, new ArrayList<>());\n        }\n        this.beds.get(bed).add(plant);\n    }\n\n    public ArrayList<String> plantsIn(String bed) {\n        return this.beds.getOrDefault(bed, new ArrayList<>());\n    }\n\n    public int totalPlants() {\n        int total = 0;\n        for (ArrayList<String> plants : this.beds.values()) {\n            total += plants.size();\n        }\n        return total;\n    }\n\n    public int bedCount() {\n        return this.beds.size();\n    }\n}\n","solution":"import java.util.ArrayList;\n\npublic class Main {\n    public static void main(String[] args) {\n        Garden garden = new Garden();\n        garden.plant(\"North\", \"kale\");\n        garden.plant(\"North\", \"leeks\");\n        garden.plant(\"Herb\", \"mint\");\n        garden.plant(\"Herb\", \"sage\");\n\n        ArrayList<String> north = garden.plantsIn(\"North\");\n        north.add(\"nettles\");\n        ArrayList<String> herbs = garden.plantsIn(\"Herb\");\n        garden.plantsIn(\"Herb\").clear();\n\n        System.out.println(garden.plantsIn(\"North\"));\n        System.out.println(herbs.size() + \" \" + garden.totalPlants());\n        System.out.println(garden.bedCount());\n    }\n}\n// ==== Garden.java ====\nimport java.util.ArrayList;\nimport java.util.HashMap;\n\npublic class Garden {\n    private HashMap<String, ArrayList<String>> beds;\n\n    public Garden() {\n        this.beds = new HashMap<>();\n    }\n\n    public void plant(String bed, String plant) {\n        if (!this.beds.containsKey(bed)) {\n            this.beds.put(bed, new ArrayList<>());\n        }\n        this.beds.get(bed).add(plant);\n    }\n\n    public ArrayList<String> plantsIn(String bed) {\n        return this.beds.getOrDefault(bed, new ArrayList<>());\n    }\n\n    public int totalPlants() {\n        int total = 0;\n        for (ArrayList<String> plants : this.beds.values()) {\n            total += plants.size();\n        }\n        return total;\n    }\n\n    public int bedCount() {\n        return this.beds.size();\n    }\n}\n","hints":["`plantsIn(\"North\")` returns the North bed's list itself, so `north` refers to the list in the map, and the nettles go into the garden too.","`herbs` and the second `plantsIn(\"Herb\")` are the same list, the one in the map, so `clear()` empties it for both. The Herb bed is still a key, now with an empty list: nothing in Garden removed it."],"tests":[{"name":"Output","stdin":"","expect":"[kale, leeks, nettles]\n0 3\n2","hidden":false}],"require":[],"forbid":[],"lines":["[kale, leeks, nettles]","0 3","2"]},{"task":"The restaurant needs two more methods in `TableOrders`. The class from the first task is ready. Add:\n\n- `countOf(String dish)`, which returns how many of that dish have been ordered at all the tables together.\n- `tablesWith(String dish)`, which returns a new list of the tables that have ordered the dish, from the smallest number to the largest. A table that ordered the dish more than once is on the list once.\n\n`Main.java` is a test drive, and it prints:\n\n```\n4\n[3, 12, 21]\n[3] []\n```\n","kind":"code","seed":"// ==== TableOrders.java ====\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\n\npublic class TableOrders {\n    private HashMap<Integer, ArrayList<String>> orders;\n\n    public TableOrders() {\n        this.orders = new HashMap<>();\n    }\n\n    public void add(int table, String dish) {\n        if (!this.orders.containsKey(table)) {\n            this.orders.put(table, new ArrayList<>());\n        }\n        this.orders.get(table).add(dish);\n    }\n\n    public ArrayList<String> dishesAt(int table) {\n        return this.orders.getOrDefault(table, new ArrayList<>());\n    }\n\n    public ArrayList<Integer> tables() {\n        ArrayList<Integer> tables = new ArrayList<>(this.orders.keySet());\n        Collections.sort(tables);\n        return tables;\n    }\n\n    // Write the methods countOf and tablesWith here.\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        TableOrders orders = new TableOrders();\n        orders.add(12, \"soup\");\n        orders.add(3, \"pie\");\n        orders.add(21, \"soup\");\n        orders.add(3, \"soup\");\n        orders.add(21, \"soup\");\n        System.out.println(orders.countOf(\"soup\"));\n        System.out.println(orders.tablesWith(\"soup\"));\n        System.out.println(orders.tablesWith(\"pie\") + \" \" + orders.tablesWith(\"tea\"));\n    }\n}\n","solution":"// ==== TableOrders.java ====\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\n\npublic class TableOrders {\n    private HashMap<Integer, ArrayList<String>> orders;\n\n    public TableOrders() {\n        this.orders = new HashMap<>();\n    }\n\n    public void add(int table, String dish) {\n        if (!this.orders.containsKey(table)) {\n            this.orders.put(table, new ArrayList<>());\n        }\n        this.orders.get(table).add(dish);\n    }\n\n    public ArrayList<String> dishesAt(int table) {\n        return this.orders.getOrDefault(table, new ArrayList<>());\n    }\n\n    public ArrayList<Integer> tables() {\n        ArrayList<Integer> tables = new ArrayList<>(this.orders.keySet());\n        Collections.sort(tables);\n        return tables;\n    }\n\n    public int countOf(String dish) {\n        int count = 0;\n        for (ArrayList<String> dishes : this.orders.values()) {\n            for (String ordered : dishes) {\n                if (ordered.equals(dish)) {\n                    count++;\n                }\n            }\n        }\n        return count;\n    }\n\n    public ArrayList<Integer> tablesWith(String dish) {\n        ArrayList<Integer> found = new ArrayList<>();\n        for (int table : this.tables()) {\n            if (this.orders.get(table).contains(dish)) {\n                found.add(table);\n            }\n        }\n        return found;\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        TableOrders orders = new TableOrders();\n        orders.add(12, \"soup\");\n        orders.add(3, \"pie\");\n        orders.add(21, \"soup\");\n        orders.add(3, \"soup\");\n        orders.add(21, \"soup\");\n        System.out.println(orders.countOf(\"soup\"));\n        System.out.println(orders.tablesWith(\"soup\"));\n        System.out.println(orders.tablesWith(\"pie\") + \" \" + orders.tablesWith(\"tea\"));\n    }\n}\n","hints":["`countOf` has to look at every table. Go through the lists with `for (ArrayList<String> dishes : this.orders.values())`, and count the dishes in each one that equal the one asked for. The order of the tables doesn't matter for a count.","For `tablesWith`, go through `this.tables()`, which is already sorted, and add a table to a new list when its dishes `contains` the dish. Then each table is checked once, so it's on the list at most once, and the list comes out sorted."],"tests":[{"name":"The test drive","stdin":"","expect":"4\n[3, 12, 21]\n[3] []","hidden":false},{"name":"Test 2","stdin":"","call":"TableOrders orders = new TableOrders();\nSystem.out.println(orders.countOf(\"tea\") + \" \" + orders.tablesWith(\"tea\"));\norders.add(17, \"tea\");\norders.add(5, \"tea\");\norders.add(30, \"cake\");\norders.add(16, \"tea\");\norders.add(5, \"tea\");\nSystem.out.println(orders.countOf(\"tea\") + \" \" + orders.countOf(\"cake\"));\nSystem.out.println(orders.tablesWith(\"tea\") + \" \" + orders.tablesWith(\"cake\"));\n","expect":"0 []\n4 1\n[5, 16, 17] [30]","hidden":true},{"name":"Test 3","stdin":"","call":"TableOrders orders = new TableOrders();\norders.add(40, \"juice\");\norders.add(40, \"juice\");\norders.add(8, \"juice\");\norders.add(8, \"soup\");\nSystem.out.println(orders.tablesWith(\"juice\") + \" \" + orders.countOf(\"juice\"));\nSystem.out.println(orders.tablesWith(\"Juice\") + \" \" + orders.countOf(\"soup\") + \" \" + orders.tables());\n","expect":"[8, 40] 3\n[] 1 [8, 40]","hidden":true}],"require":[],"forbid":[{"pattern":"\\bclass\\s+TableOrders\\b[\\s\\S]*\\bSystem\\s*\\.\\s*out\\b[\\s\\S]*\\bclass\\s+Main\\b","message":"TableOrders doesn't print. Its methods return their answers, and `main` prints them."}]}]}