{"id":"interfaces-set","slug":"set-interface","title":"Java's Set interface and HashSet","text":"### Values kept once\n\nThe smart home app keeps a log: every time a device reports in, the log gets the device's name. The heater reports again and again, but at the end of the day the app wants to know which devices reported in, each just once. A list keeps every report, repeats too. A **set** keeps each value only once. `Set` is an interface in `java.util`, and `HashSet` is a class that implements it:\n\n```java\nimport java.util.HashSet;\nimport java.util.Set;\n\npublic class Main {\n    public static void main(String[] args) {\n        Set<String> devices = new HashSet<>();\n        devices.add(\"heater\");\n        devices.add(\"fan\");\n        devices.add(\"heater\");\n        System.out.println(devices.size());\n        System.out.println(devices.contains(\"fan\"));\n        System.out.println(devices.contains(\"sprinkler\"));\n        System.out.println(devices.add(\"sprinkler\"));\n        System.out.println(devices.add(\"heater\"));\n        devices.remove(\"fan\");\n        System.out.println(devices.size());\n    }\n}\n```\n\n```output\n2\ntrue\nfalse\ntrue\nfalse\n2\n```\n\n- `Set<String> devices = new HashSet<>();` follows the pattern of the lists and maps before it: the interface as the variable's type, the class after `new`. Both need an import.\n- `add` adds a value that isn't in the set yet. A value that's already there isn't added again: the second heater changes nothing, so the size is 2.\n- `add` also returns a boolean: `true` when it added the value, and `false` when the value was there already. Programs often ignore it, but it's handy for spotting repeats.\n- `contains`, `remove(value)`, `size`, `isEmpty` and `clear` work as they do for a list.\n- A set has no `get(index)`, no `indexOf` and no `remove(index)`: its values have no positions. So in a `Set<Integer>`, `remove(7)` removes the value 7. Module 14's trap with number lists doesn't apply to sets.\n\n### How a HashSet finds its values\n\nA HashSet keeps its values the way a HashMap keeps its keys: in buckets chosen by their hash codes (module 32). So its `contains` is fast even in a big set, where a list's `contains` looks at every value in turn. For the same reason, a HashSet needs values whose `hashCode` and `equals` agree. Strings and Integers have both. Objects of your own classes need both, just as the keys of a hash map do. In fact, a map's `keySet()` is a Set: a map keeps each key once, too.\n\n### The order of a set\n\nA for-each loop goes through a HashSet as a HashMap goes through its keys: in an order set by the hash codes. That order may happen to match the order the values were added, or alphabetical order, but Java makes no promise about it, so never rely on it. That's why this course never prints a whole set of several values. To show them in a fixed order, copy them into a list and sort it, as module 31 did with keys:\n\n```java\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\npublic class Main {\n    public static void main(String[] args) {\n        String[] reports = {\"fan\", \"heater\", \"lamp\", \"heater\", \"fan\", \"heater\"};\n        Set<String> devices = new HashSet<>();\n        for (String device : reports) {\n            devices.add(device);\n        }\n        List<String> sorted = new ArrayList<>(devices);\n        Collections.sort(sorted);\n        System.out.println(reports.length + \" reports from \" + devices.size() + \" devices\");\n        System.out.println(sorted);\n    }\n}\n```\n\n```output\n6 reports from 3 devices\n[fan, heater, lamp]\n```\n\n- `new ArrayList<>(devices)` makes a list of the set's values, just as `new ArrayList<>(map.keySet())` does for a map's keys. `Collections.sort` then puts them in order.\n- It works the other way round too: `new HashSet<>(list)` makes a set of a list's values, each once.\n","task":"Write the method `countDevices(List<String> reports)`, which returns how many different devices are in a log of reports. Use a HashSet. The check runs:\n\n```java\nList<String> reports = new ArrayList<>();\nreports.add(\"heater\");\nreports.add(\"doorbell\");\nreports.add(\"heater\");\nreports.add(\"fan\");\nreports.add(\"heater\");\nSystem.out.println(countDevices(reports));\n```\n\nand expects:\n\n```\n3\n```\n","kind":"code","seed":"import java.util.ArrayList;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\npublic class Main {\n    public static void main(String[] args) {\n        List<String> reports = new ArrayList<>();\n        reports.add(\"heater\");\n        reports.add(\"doorbell\");\n        reports.add(\"heater\");\n        reports.add(\"fan\");\n        reports.add(\"heater\");\n        System.out.println(countDevices(reports));\n    }\n\n    // Write the method countDevices here.\n}\n","solution":"import java.util.ArrayList;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\npublic class Main {\n    public static void main(String[] args) {\n        List<String> reports = new ArrayList<>();\n        reports.add(\"heater\");\n        reports.add(\"doorbell\");\n        reports.add(\"heater\");\n        reports.add(\"fan\");\n        reports.add(\"heater\");\n        System.out.println(countDevices(reports));\n    }\n\n    public static int countDevices(List<String> reports) {\n        Set<String> devices = new HashSet<>();\n        for (String device : reports) {\n            devices.add(device);\n        }\n        return devices.size();\n    }\n}\n","hints":["The header is `public static int countDevices(List<String> reports)`. Make a `Set<String> devices = new HashSet<>();`, and add every report to it.","The set keeps each device once, whatever you add, so the answer is `devices.size()`."],"tests":[{"name":"Test 1","stdin":"","call":"List<String> reports = new ArrayList<>();\nreports.add(\"heater\");\nreports.add(\"doorbell\");\nreports.add(\"heater\");\nreports.add(\"fan\");\nreports.add(\"heater\");\nSystem.out.println(countDevices(reports));\n","expect":"3","hidden":false},{"name":"Test 2","stdin":"","call":"List<String> none = new ArrayList<>();\nSystem.out.println(countDevices(none));\nArrayList<String> same = new ArrayList<>();\nfor (int i = 0; i < 5; i++) {\n    same.add(\"camera\");\n}\nSystem.out.println(countDevices(same));\n","expect":"0\n1","hidden":true},{"name":"Test 3","stdin":"","call":"List<String> reports = new ArrayList<>();\nString[] names = {\"lamp\", \"Lamp\", \"lamp \", \"kettle\", \"lamp\", \"radio\", \"kettle\"};\nfor (String name : names) {\n    reports.add(name);\n}\nSystem.out.println(countDevices(reports) + \" \" + reports.size());\n","expect":"5 7","hidden":true}],"require":[{"pattern":"\\bpublic\\s+static\\s+int\\s+countDevices\\s*\\(\\s*List<String>\\s+\\w+\\s*\\)","message":"Write the method as `public static int countDevices(List<String> reports)`."},{"pattern":"\\bnew\\s+HashSet\\b","message":"Use a HashSet: it keeps each device once."}],"forbid":[],"more":[{"task":"The door of a conference hall scans each visitor's badge number, every time the visitor comes in. What does this program print?\n","kind":"predict","seed":"import java.util.HashSet;\nimport java.util.Set;\n\npublic class Main {\n    public static void main(String[] args) {\n        Set<Integer> badges = new HashSet<>();\n        int[] scans = {105, 212, 105, 330, 212, 105};\n        int repeats = 0;\n        for (int scan : scans) {\n            if (!badges.add(scan)) {\n                repeats++;\n            }\n        }\n        System.out.println(badges.size() + \" \" + repeats);\n        badges.remove(212);\n        System.out.println(badges.contains(212) + \" \" + badges.contains(330));\n    }\n}\n","solution":"import java.util.HashSet;\nimport java.util.Set;\n\npublic class Main {\n    public static void main(String[] args) {\n        Set<Integer> badges = new HashSet<>();\n        int[] scans = {105, 212, 105, 330, 212, 105};\n        int repeats = 0;\n        for (int scan : scans) {\n            if (!badges.add(scan)) {\n                repeats++;\n            }\n        }\n        System.out.println(badges.size() + \" \" + repeats);\n        badges.remove(212);\n        System.out.println(badges.contains(212) + \" \" + badges.contains(330));\n    }\n}\n","hints":["`add` returns `false` when the number is in the set already, and then `repeats` goes up.","The first 105, 212 and 330 are new; the other three scans are repeats, and the set holds three numbers. A set has no indexes, so `remove(212)` removes the value 212, not index 212. Then `contains(212)` is false, while 330 is still there."],"tests":[{"name":"Output","stdin":"","expect":"3 3\nfalse true","hidden":false}],"require":[],"forbid":[],"lines":["3 3","false true"]},{"task":"Write the method `reportedTwice(List<String> reports)`, which returns a list of the devices that reported in more than once, each device once, in alphabetical order. Use two HashSets: one for the devices seen so far, and one for those seen again. The check runs:\n\n```java\nList<String> reports = new ArrayList<>();\nreports.add(\"sprinkler\");\nreports.add(\"heater\");\nreports.add(\"camera\");\nreports.add(\"heater\");\nreports.add(\"sprinkler\");\nreports.add(\"heater\");\nSystem.out.println(reportedTwice(reports));\n```\n\nand expects:\n\n```\n[heater, sprinkler]\n```\n","kind":"code","seed":"import java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\npublic class Main {\n    public static void main(String[] args) {\n        List<String> reports = new ArrayList<>();\n        reports.add(\"sprinkler\");\n        reports.add(\"heater\");\n        reports.add(\"camera\");\n        reports.add(\"heater\");\n        reports.add(\"sprinkler\");\n        reports.add(\"heater\");\n        System.out.println(reportedTwice(reports));\n    }\n\n    // Write the method reportedTwice here.\n}\n","solution":"import java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\npublic class Main {\n    public static void main(String[] args) {\n        List<String> reports = new ArrayList<>();\n        reports.add(\"sprinkler\");\n        reports.add(\"heater\");\n        reports.add(\"camera\");\n        reports.add(\"heater\");\n        reports.add(\"sprinkler\");\n        reports.add(\"heater\");\n        System.out.println(reportedTwice(reports));\n    }\n\n    public static List<String> reportedTwice(List<String> reports) {\n        Set<String> seen = new HashSet<>();\n        Set<String> again = new HashSet<>();\n        for (String device : reports) {\n            if (!seen.add(device)) {\n                again.add(device);\n            }\n        }\n        List<String> result = new ArrayList<>(again);\n        Collections.sort(result);\n        return result;\n    }\n}\n","hints":["Go through the reports. When a device is in `seen` already (or when `seen.add(device)` returns `false`), add it to the second set, `again`. The heater gets added to `again` twice, but a set keeps it once.","At the end, copy `again` into a list with `new ArrayList<>(again)`, sort it with `Collections.sort`, and return the list."],"tests":[{"name":"Test 1","stdin":"","call":"List<String> reports = new ArrayList<>();\nreports.add(\"sprinkler\");\nreports.add(\"heater\");\nreports.add(\"camera\");\nreports.add(\"heater\");\nreports.add(\"sprinkler\");\nreports.add(\"heater\");\nSystem.out.println(reportedTwice(reports));\n","expect":"[heater, sprinkler]","hidden":false},{"name":"Test 2","stdin":"","call":"List<String> once = new ArrayList<>();\nonce.add(\"kettle\");\nonce.add(\"fan\");\nSystem.out.println(reportedTwice(once));\nonce.add(\"fan\");\nonce.add(\"fan\");\nSystem.out.println(reportedTwice(once));\n","expect":"[]\n[fan]","hidden":true},{"name":"Test 3","stdin":"","call":"List<String> reports = new ArrayList<>();\nString[] names = {\"oven\", \"fridge\", \"blinds\", \"oven\", \"radio\", \"fridge\", \"blinds\", \"router\", \"radio\"};\nfor (String name : names) {\n    reports.add(name);\n}\nSystem.out.println(reportedTwice(reports) + \" \" + reports.size());\n","expect":"[blinds, fridge, oven, radio] 9","hidden":true}],"require":[{"pattern":"\\bpublic\\s+static\\s+List<String>\\s+reportedTwice\\s*\\(\\s*List<String>\\s+\\w+\\s*\\)","message":"Write the method as `public static List<String> reportedTwice(List<String> reports)`."},{"pattern":"\\bnew\\s+HashSet\\b","min":2,"message":"Use two HashSets: one for the devices seen so far, and one for those seen again."}],"forbid":[]}]}