{"id":"grouping-counting","slug":"counting-and-grouping","title":"Counting and grouping","text":"Step 1 added to the lists one call at a time. Often the data comes all at once, in a list, an array or a file, and a loop sorts it into groups. This step does that, first with counts and then with lists.\n\n### Counting with a map\n\nModule 31 counted with a map: a `HashMap<String, Integer>`, where each thing adds one to its count, with `getOrDefault(key, 0)` for a key that hasn't come up yet. Here the same counts how many words start with each letter:\n\n```java\nString[] words = {\"sun\", \"moon\", \"sea\", \"star\", \"mist\"};\nHashMap<String, Integer> counts = new HashMap<>();\nfor (String word : words) {\n    String first = word.substring(0, 1);\n    int count = counts.getOrDefault(first, 0);\n    counts.put(first, count + 1);\n}\nSystem.out.println(counts.get(\"s\"));\nSystem.out.println(counts.get(\"m\"));\nSystem.out.println(counts.getOrDefault(\"x\", 0));\n```\n\n```output\n3\n2\n0\n```\n\n`word.substring(0, 1)` is the word's first letter, as a String (module 16). The counts say how many words start with each letter, but not which words.\n\n### Grouping: a list for each key\n\nInstead of counting the words of each letter, the program can keep them. The value becomes a list, and each word goes on the list of its first letter. It's step 1's `join`, in a loop:\n\n```java\nString[] words = {\"sun\", \"moon\", \"sea\", \"star\", \"mist\"};\nHashMap<String, ArrayList<String>> byLetter = new HashMap<>();\nfor (String word : words) {\n    String first = word.substring(0, 1);\n    if (!byLetter.containsKey(first)) {\n        byLetter.put(first, new ArrayList<>());\n    }\n    byLetter.get(first).add(word);\n}\nSystem.out.println(byLetter.get(\"s\"));\nSystem.out.println(byLetter.get(\"m\"));\nSystem.out.println(byLetter.get(\"s\").size());\n```\n\n```output\n[sun, sea, star]\n[moon, mist]\n3\n```\n\nCounting and grouping go together: the size of a group is its count.\n\n### Every group, in a fixed order\n\nTo print every group, go through the keys. But a HashMap goes through its keys in an order of its own, set by the keys' hash codes, and not in the order they were added. For String keys that order is the same on every run, but it's hard to predict, and adding more keys can change it. So it can't be relied on. When the order matters, sort the keys first, as module 31 did: copy them into a list, and sort the list with `Collections.sort`.\n\n```java\nString[] words = {\"tulip\", \"iris\", \"rose\", \"tansy\", \"ivy\", \"rue\", \"aster\"};\nHashMap<String, ArrayList<String>> byLetter = new HashMap<>();\nfor (String word : words) {\n    String first = word.substring(0, 1);\n    if (!byLetter.containsKey(first)) {\n        byLetter.put(first, new ArrayList<>());\n    }\n    byLetter.get(first).add(word);\n}\n\nArrayList<String> letters = new ArrayList<>(byLetter.keySet());\nCollections.sort(letters);\nfor (String letter : letters) {\n    System.out.println(letter + \": \" + byLetter.get(letter));\n}\n```\n\n```output\na: [aster]\ni: [iris, ivy]\nr: [rose, rue]\nt: [tulip, tansy]\n```\n\n- `new ArrayList<>(byLetter.keySet())` makes a new list that holds the map's keys. It's the same as creating an empty list and adding each key in a for-each loop over `keySet()`.\n- Sorting that list doesn't change the map. The loop then goes through the letters in alphabetical order, and gets each letter's group with `get`.\n- Within each group, the words keep the order they were added in. Only the order of the keys needed fixing.\n\n### Grouping objects by one of their values\n\nThe same pattern groups objects. Each Plant knows its name and the garden bed it grows in, and grouping the plants by bed gives a map from a bed to the list of its plants:\n\n```java\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\n\npublic class Main {\n    public static void main(String[] args) {\n        ArrayList<Plant> plants = new ArrayList<>();\n        plants.add(new Plant(\"kale\", \"North\"));\n        plants.add(new Plant(\"basil\", \"Herb\"));\n        plants.add(new Plant(\"leeks\", \"North\"));\n        plants.add(new Plant(\"thyme\", \"Herb\"));\n        plants.add(new Plant(\"beans\", \"South\"));\n\n        HashMap<String, ArrayList<Plant>> byBed = groupByBed(plants);\n        ArrayList<String> beds = new ArrayList<>(byBed.keySet());\n        Collections.sort(beds);\n        for (String bed : beds) {\n            System.out.println(bed + \": \" + byBed.get(bed));\n        }\n    }\n\n    public static HashMap<String, ArrayList<Plant>> groupByBed(ArrayList<Plant> plants) {\n        HashMap<String, ArrayList<Plant>> byBed = new HashMap<>();\n        for (Plant plant : plants) {\n            String bed = plant.getBed();\n            if (!byBed.containsKey(bed)) {\n                byBed.put(bed, new ArrayList<>());\n            }\n            byBed.get(bed).add(plant);\n        }\n        return byBed;\n    }\n}\n// ==== Plant.java ====\npublic class Plant {\n    private String name;\n    private String bed;\n\n    public Plant(String name, String bed) {\n        this.name = name;\n        this.bed = bed;\n    }\n\n    public String getName() {\n        return this.name;\n    }\n\n    public String getBed() {\n        return this.bed;\n    }\n\n    public String toString() {\n        return this.name;\n    }\n}\n```\n\n```output\nHerb: [basil, thyme]\nNorth: [kale, leeks]\nSouth: [beans]\n```\n\n- The values are lists of Plant objects, `ArrayList<Plant>`. Printing a list prints each plant with its `toString`.\n- The key is one of the object's values, `plant.getBed()`. The lists hold the objects themselves, so everything about each plant is still at hand, not only its name.\n- `groupByBed` builds a new map and returns it. The list of plants doesn't change.\n","task":"A restaurant's kitchen gets the orders of an evening, one per line: a table number and a dish, separated by a comma, like `4,soup`. An empty line ends the evening. Group the orders by dish in a `HashMap<String, ArrayList<Integer>>`: each dish's list holds the table of every order of it, in the order read. Then print each dish with how many times it was ordered (the size of its list) and its tables, like `soup: 2, tables [4, 9]`, with the dishes in alphabetical order. Sort the dish names with `Collections.sort`. For the input shown, the program prints:\n\n```\ncurry: 3, tables [9, 4, 4]\nsalad: 1, tables [2]\nsoup: 2, tables [4, 9]\n```\n","kind":"code","seed":"import java.util.ArrayList;\nimport java.util.Collections;\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, ArrayList<Integer>> tablesByDish = new HashMap<>();\n\n    }\n}\n","solution":"import java.util.ArrayList;\nimport java.util.Collections;\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, ArrayList<Integer>> tablesByDish = new HashMap<>();\n        while (true) {\n            String line = scanner.nextLine();\n            if (line.isEmpty()) {\n                break;\n            }\n            String[] parts = line.split(\",\");\n            int table = Integer.valueOf(parts[0]);\n            String dish = parts[1];\n            if (!tablesByDish.containsKey(dish)) {\n                tablesByDish.put(dish, new ArrayList<>());\n            }\n            tablesByDish.get(dish).add(table);\n        }\n\n        ArrayList<String> dishes = new ArrayList<>(tablesByDish.keySet());\n        Collections.sort(dishes);\n        for (String dish : dishes) {\n            ArrayList<Integer> tables = tablesByDish.get(dish);\n            System.out.println(dish + \": \" + tables.size() + \", tables \" + tables);\n        }\n    }\n}\n","hints":["Read in a `while (true)` loop that stops with `break` at the empty line. `line.split(\",\")` gives the table in `parts[0]`, to turn into a number with `Integer.valueOf`, and the dish in `parts[1]`. Add the table to the dish's list, and create the list the first time the dish comes up.","After the loop, copy the keys into a list with `new ArrayList<>(tablesByDish.keySet())` and sort it with `Collections.sort`. For each dish, `tablesByDish.get(dish)` is its list of tables, and its `size()` is the count."],"tests":[{"name":"Test 1","stdin":"4,soup\n9,curry\n4,curry\n2,salad\n9,soup\n4,curry\n\n","expect":"curry: 3, tables [9, 4, 4]\nsalad: 1, tables [2]\nsoup: 2, tables [4, 9]","hidden":false},{"name":"Test 2","stdin":"7,tea\n\n","expect":"tea: 1, tables [7]","hidden":true},{"name":"Test 3","stdin":"3,pie\n11,waffles\n3,bagel\n8,tea\n1,omelet\n11,pie\n8,tea\n20,juice\n3,pie\n\n","expect":"bagel: 1, tables [3]\njuice: 1, tables [20]\nomelet: 1, tables [1]\npie: 3, tables [3, 11, 3]\ntea: 2, tables [8, 8]\nwaffles: 1, tables [11]","hidden":true}],"require":[{"pattern":"\\bHashMap\\s*<\\s*String\\s*,\\s*ArrayList\\s*<\\s*Integer\\s*>\\s*>","message":"Group the tables of each dish in a `HashMap<String, ArrayList<Integer>>`."},{"pattern":"\\bCollections\\s*\\.\\s*sort\\s*\\(","message":"Sort the dish names with `Collections.sort` before printing them."}],"forbid":[],"more":[{"task":"A shopping list is grouped by the first letter of each item. What does this program print? Look closely at every word's first letter.\n","kind":"predict","seed":"import java.util.ArrayList;\nimport java.util.HashMap;\n\npublic class Main {\n    public static void main(String[] args) {\n        String[] items = {\"apple\", \"bread\", \"Avocado\", \"butter\", \"apple\", \"cheese\"};\n        HashMap<String, ArrayList<String>> groups = new HashMap<>();\n        for (String item : items) {\n            String first = item.substring(0, 1);\n            if (!groups.containsKey(first)) {\n                groups.put(first, new ArrayList<>());\n            }\n            groups.get(first).add(item);\n        }\n        System.out.println(groups.get(\"a\"));\n        System.out.println(groups.get(\"b\").size());\n        System.out.println(groups.size());\n        System.out.println(groups.containsKey(\"A\"));\n    }\n}\n","solution":"import java.util.ArrayList;\nimport java.util.HashMap;\n\npublic class Main {\n    public static void main(String[] args) {\n        String[] items = {\"apple\", \"bread\", \"Avocado\", \"butter\", \"apple\", \"cheese\"};\n        HashMap<String, ArrayList<String>> groups = new HashMap<>();\n        for (String item : items) {\n            String first = item.substring(0, 1);\n            if (!groups.containsKey(first)) {\n                groups.put(first, new ArrayList<>());\n            }\n            groups.get(first).add(item);\n        }\n        System.out.println(groups.get(\"a\"));\n        System.out.println(groups.get(\"b\").size());\n        System.out.println(groups.size());\n        System.out.println(groups.containsKey(\"A\"));\n    }\n}\n","hints":["A list can hold the same value twice, so both apples go on the list of `a`. `Avocado` starts with a capital A, and `\"A\"` is a different String from `\"a\"`, so it's a different key.","Count the keys: `a`, `b`, `A` and `c`. `groups.size()` is the number of keys, not the number of items."],"tests":[{"name":"Output","stdin":"","expect":"[apple, apple]\n2\n4\ntrue","hidden":false}],"require":[],"forbid":[],"lines":["[apple, apple]","2","4","true"]},{"task":"The music app now keeps its songs as objects. `Song.java` is ready: a song has a title, an album and a length in seconds, with the getters `getTitle()`, `getAlbum()` and `getSeconds()`, and its `toString` gives the title. In `Main.java`, write the method `byAlbum(ArrayList<Song> songs)`, which returns a new `HashMap<String, ArrayList<Song>>` from each album to the list of its songs, in the order they are in `songs`. The check runs:\n\n```java\nArrayList<Song> songs = new ArrayList<>();\nsongs.add(new Song(\"Crosswalk\", \"Green Signal\", 214));\nsongs.add(new Song(\"Porch Swing\", \"Long Weekend\", 187));\nsongs.add(new Song(\"Rush Hour\", \"Green Signal\", 243));\nsongs.add(new Song(\"Cloud Shapes\", \"Long Weekend\", 205));\nsongs.add(new Song(\"Detour\", \"Green Signal\", 198));\nHashMap<String, ArrayList<Song>> albums = byAlbum(songs);\nSystem.out.println(albums.get(\"Green Signal\"));\nSystem.out.println(albums.get(\"Long Weekend\"));\n```\n\nand expects:\n\n```\n[Crosswalk, Rush Hour, Detour]\n[Porch Swing, Cloud Shapes]\n```\n","kind":"code","seed":"// ==== Main.java ====\nimport java.util.ArrayList;\nimport java.util.HashMap;\n\npublic class Main {\n    public static void main(String[] args) {\n        ArrayList<Song> songs = new ArrayList<>();\n        songs.add(new Song(\"Crosswalk\", \"Green Signal\", 214));\n        songs.add(new Song(\"Porch Swing\", \"Long Weekend\", 187));\n        songs.add(new Song(\"Rush Hour\", \"Green Signal\", 243));\n        songs.add(new Song(\"Cloud Shapes\", \"Long Weekend\", 205));\n        songs.add(new Song(\"Detour\", \"Green Signal\", 198));\n        HashMap<String, ArrayList<Song>> albums = byAlbum(songs);\n        System.out.println(albums.get(\"Green Signal\"));\n        System.out.println(albums.get(\"Long Weekend\"));\n    }\n\n    // Write the method byAlbum here.\n}\n\n// ==== Song.java ====\npublic class Song {\n    private String title;\n    private String album;\n    private int seconds;\n\n    public Song(String title, String album, int seconds) {\n        this.title = title;\n        this.album = album;\n        this.seconds = seconds;\n    }\n\n    public String getTitle() {\n        return this.title;\n    }\n\n    public String getAlbum() {\n        return this.album;\n    }\n\n    public int getSeconds() {\n        return this.seconds;\n    }\n\n    public String toString() {\n        return this.title;\n    }\n}\n","solution":"// ==== Main.java ====\nimport java.util.ArrayList;\nimport java.util.HashMap;\n\npublic class Main {\n    public static void main(String[] args) {\n        ArrayList<Song> songs = new ArrayList<>();\n        songs.add(new Song(\"Crosswalk\", \"Green Signal\", 214));\n        songs.add(new Song(\"Porch Swing\", \"Long Weekend\", 187));\n        songs.add(new Song(\"Rush Hour\", \"Green Signal\", 243));\n        songs.add(new Song(\"Cloud Shapes\", \"Long Weekend\", 205));\n        songs.add(new Song(\"Detour\", \"Green Signal\", 198));\n        HashMap<String, ArrayList<Song>> albums = byAlbum(songs);\n        System.out.println(albums.get(\"Green Signal\"));\n        System.out.println(albums.get(\"Long Weekend\"));\n    }\n\n    public static HashMap<String, ArrayList<Song>> byAlbum(ArrayList<Song> songs) {\n        HashMap<String, ArrayList<Song>> albums = new HashMap<>();\n        for (Song song : songs) {\n            String album = song.getAlbum();\n            if (!albums.containsKey(album)) {\n                albums.put(album, new ArrayList<>());\n            }\n            albums.get(album).add(song);\n        }\n        return albums;\n    }\n}\n\n// ==== Song.java ====\npublic class Song {\n    private String title;\n    private String album;\n    private int seconds;\n\n    public Song(String title, String album, int seconds) {\n        this.title = title;\n        this.album = album;\n        this.seconds = seconds;\n    }\n\n    public String getTitle() {\n        return this.title;\n    }\n\n    public String getAlbum() {\n        return this.album;\n    }\n\n    public int getSeconds() {\n        return this.seconds;\n    }\n\n    public String toString() {\n        return this.title;\n    }\n}\n","hints":["It's `groupByBed` from above, for songs. Create the map, go through the songs with a for-each loop, and use `song.getAlbum()` as the key.","Put a new list in the map when the album isn't a key yet, and then add the Song object itself to `albums.get(album)`. Return the map after the loop."],"tests":[{"name":"Test 1","stdin":"","call":"ArrayList<Song> songs = new ArrayList<>();\nsongs.add(new Song(\"Crosswalk\", \"Green Signal\", 214));\nsongs.add(new Song(\"Porch Swing\", \"Long Weekend\", 187));\nsongs.add(new Song(\"Rush Hour\", \"Green Signal\", 243));\nsongs.add(new Song(\"Cloud Shapes\", \"Long Weekend\", 205));\nsongs.add(new Song(\"Detour\", \"Green Signal\", 198));\nHashMap<String, ArrayList<Song>> albums = byAlbum(songs);\nSystem.out.println(albums.get(\"Green Signal\"));\nSystem.out.println(albums.get(\"Long Weekend\"));\n","expect":"[Crosswalk, Rush Hour, Detour]\n[Porch Swing, Cloud Shapes]","hidden":false},{"name":"Test 2","stdin":"","call":"ArrayList<Song> songs = new ArrayList<>();\nsongs.add(new Song(\"Rain\", \"Weather\", 200));\nsongs.add(new Song(\"Snow\", \"Weather\", 185));\nsongs.add(new Song(\"Drip\", \"Caves\", 300));\nsongs.add(new Song(\"Fog\", \"Weather\", 240));\nHashMap<String, ArrayList<Song>> albums = byAlbum(songs);\nint total = 0;\nfor (Song song : albums.get(\"Weather\")) {\n    total += song.getSeconds();\n}\nSystem.out.println(albums.size() + \" \" + total);\nSystem.out.println(albums.get(\"Caves\") + \" \" + songs.size());\n","expect":"2 625\n[Drip] 4","hidden":true},{"name":"Test 3","stdin":"","call":"ArrayList<Song> songs = new ArrayList<>();\nSystem.out.println(byAlbum(songs).size());\nsongs.add(new Song(\"Zero\", \"Numbers\", 100));\nsongs.add(new Song(\"Alpha\", \"Letters\", 110));\nsongs.add(new Song(\"One\", \"Numbers\", 120));\nsongs.add(new Song(\"Mono\", \"Single\", 130));\nHashMap<String, ArrayList<Song>> albums = byAlbum(songs);\nArrayList<String> names = new ArrayList<>(albums.keySet());\nCollections.sort(names);\nfor (String name : names) {\n    System.out.println(name + \": \" + albums.get(name));\n}\n","expect":"0\nLetters: [Alpha]\nNumbers: [Zero, One]\nSingle: [Mono]","hidden":true}],"require":[{"pattern":"\\bpublic\\s+static\\s+HashMap\\s*<\\s*String\\s*,\\s*ArrayList\\s*<\\s*Song\\s*>\\s*>\\s+byAlbum\\s*\\(\\s*ArrayList\\s*<\\s*Song\\s*>\\s+\\w+\\s*\\)","message":"Write the method as `public static HashMap<String, ArrayList<Song>> byAlbum(ArrayList<Song> songs)`."}],"forbid":[]}]}