{"id":"linked-structures-tree-map-set","slug":"treemap-and-treeset","title":"TreeMap and TreeSet","text":"Java's `TreeMap` and `TreeSet`, in `java.util`, keep their keys in a binary search tree that rebalances itself (a red-black tree). Their documentation promises that `get`, `put`, `containsKey` and `remove` (`add`, `contains` and `remove` for a TreeSet) take time proportional to log n, the logarithm of the size n (the base-2 logarithm of a million is about 20). A HashMap's documentation promises constant time for `get` and `put` when the hash codes spread the keys well, but a HashMap has no order. A tree map is the one to use when the order matters.\n\n### TreeMap\n\nModule 57 used a TreeMap to go through its keys in order. Its keys are kept sorted by their `compareTo`: numbers from smallest to biggest, Strings in alphabetical order (by character code, so capital letters come before small ones). Printing a TreeMap, or going through `keySet()` or `entrySet()`, goes in that order. Its keys must be comparable: Strings and numbers are, and objects of your own class need to implement `Comparable` (module 38), or the TreeMap needs a Comparator, given to its constructor.\n\nA sorted map can answer questions a HashMap can't:\n\n- `firstKey()` and `lastKey()` are the smallest and the biggest key;\n- `floorKey(key)` is the biggest key that is at most `key`, and `ceilingKey(key)` the smallest that is at least `key`. Both return `null` when there's no such key;\n- `lowerKey(key)` and `higherKey(key)` are the same, but strictly smaller or bigger.\n\nA radio station's schedule maps each show's start time to the show. The show on the air at 10:30 is the one with the latest start time at or before 10:30, which is exactly `floorKey`:\n\n```java\nimport java.util.TreeMap;\n\npublic class Main {\n    public static void main(String[] args) {\n        TreeMap<String, String> schedule = new TreeMap<>();\n        schedule.put(\"09:00\", \"Morning music\");\n        schedule.put(\"06:00\", \"News and weather\");\n        schedule.put(\"12:00\", \"Lunch talk\");\n        schedule.put(\"13:00\", \"Afternoon music\");\n        System.out.println(schedule);\n        System.out.println(\"First show at \" + schedule.firstKey());\n        System.out.println(\"Last show at \" + schedule.lastKey());\n        String start = schedule.floorKey(\"10:30\");\n        System.out.println(\"At 10:30: \" + schedule.get(start));\n        System.out.println(\"Next start after 10:30: \" + schedule.ceilingKey(\"10:30\"));\n        System.out.println(schedule.floorKey(\"05:00\"));\n    }\n}\n```\n\n```output\n{06:00=News and weather, 09:00=Morning music, 12:00=Lunch talk, 13:00=Afternoon music}\nFirst show at 06:00\nLast show at 13:00\nAt 10:30: Morning music\nNext start after 10:30: 12:00\nnull\n```\n\nThe times are Strings, compared character by character. That works because they all have the same form, two digits, a colon and two digits: the first character that differs decides, and it's the most significant one. `\"9:00\"` would come after `\"10:30\"`, since `'9'` comes after `'1'`. Dates written as `yyyy-mm-dd`, such as `2024-05-03`, sort the same way: year first, then month, then day, all with a fixed number of digits.\n\n### TreeSet\n\nA `TreeSet` is a set that keeps its values sorted, with the same kind of methods: `first()`, `last()`, `floor(value)`, `ceiling(value)`, `lower(value)` and `higher(value)`. Like any set, it ignores a value it already has.\n\n```java\nimport java.util.TreeSet;\n\npublic class Main {\n    public static void main(String[] args) {\n        TreeSet<Integer> sizes = new TreeSet<>();\n        sizes.add(42);\n        sizes.add(38);\n        sizes.add(45);\n        sizes.add(38);\n        sizes.add(40);\n        System.out.println(sizes + \", \" + sizes.size() + \" sizes\");\n        System.out.println(sizes.first() + \" to \" + sizes.last());\n        System.out.println(sizes.floor(41) + \" \" + sizes.ceiling(41));\n        System.out.println(sizes.floor(40) + \" \" + sizes.lower(40));\n        System.out.println(sizes.ceiling(46));\n    }\n}\n```\n\n```output\n[38, 40, 42, 45], 4 sizes\n38 to 45\n40 42\n40 38\nnull\n```\n\n`floor` and `ceiling` return an Integer object, or `null`. Keep the result in an `Integer` variable (or `var`), not an `int`: putting a `null` into an `int` throws a NullPointerException.\n","task":"Count the words of a text and print them in alphabetical order. The program reads lines until the line `end`. Each line has words separated by single spaces. It counts each word in small letters, so `The` and `the` are the same word, and then prints each word and its count, like `the: 3`, in alphabetical order. Use a TreeMap. With this input:\n\n```input\nthe cat saw the dog\nThe dog ran\nend\n```\n\nthe program prints:\n\n```\ncat: 1\ndog: 2\nran: 1\nsaw: 1\nthe: 3\n```\n","kind":"code","seed":"import java.util.Scanner;\nimport java.util.TreeMap;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n\n    }\n}\n","solution":"import java.util.Scanner;\nimport java.util.TreeMap;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        TreeMap<String, Integer> counts = new TreeMap<>();\n        while (true) {\n            String line = scanner.nextLine();\n            if (line.equals(\"end\")) {\n                break;\n            }\n            for (String word : line.split(\" \")) {\n                String small = word.toLowerCase();\n                counts.put(small, counts.getOrDefault(small, 0) + 1);\n            }\n        }\n        for (String word : counts.keySet()) {\n            System.out.println(word + \": \" + counts.get(word));\n        }\n    }\n}\n","hints":["Split each line with `line.split(\" \")`, and count `word.toLowerCase()` in a `TreeMap<String, Integer>`: `counts.put(word, counts.getOrDefault(word, 0) + 1);`.","After the loop, `for (String word : counts.keySet())` goes through the words in alphabetical order, since the TreeMap keeps its keys sorted."],"tests":[{"name":"Test 1","stdin":"the cat saw the dog\nThe dog ran\nend\n","expect":"cat: 1\ndog: 2\nran: 1\nsaw: 1\nthe: 3","hidden":false},{"name":"Test 2","stdin":"Zebra apple Mango\nmango APPLE apple\nkiwi\nend\n","expect":"apple: 3\nkiwi: 1\nmango: 2\nzebra: 1","hidden":true},{"name":"Test 3","stdin":"Hello hello HELLO world\nend\n","expect":"hello: 3\nworld: 1","hidden":true}],"require":[{"pattern":"new\\s+TreeMap\\s*<","message":"Count the words in a TreeMap, which keeps them in alphabetical order."}],"forbid":[{"pattern":"\\b(sort|sorted)\\b","message":"No sorting needed: the TreeMap keeps its keys in order."}],"more":[{"task":"A beach's tide board shows, for any time you ask about, the last tide (high or low) at or before that time and the next one at or after it. The first line of the input has the tide times, in `HH:MM` form and in any order, separated by spaces. Each line after it is a time asked about, until the line `end`. For each, the program prints the time, `: previous `, the latest tide at or before it, `, next `, and the earliest tide at or after it. When there's no such tide, it prints `none` in its place. Keep the tide times in a TreeSet and use `floor` and `ceiling`. With this input:\n\n```input\n07:30 06:40 08:15 07:05\n07:20\n07:30\n06:00\n09:00\nend\n```\n\nthe program prints:\n\n```\n07:20: previous 07:05, next 07:30\n07:30: previous 07:30, next 07:30\n06:00: previous none, next 06:40\n09:00: previous 08:15, next none\n```\n","kind":"code","seed":"import java.util.Scanner;\nimport java.util.TreeSet;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n\n    }\n}\n","solution":"import java.util.Scanner;\nimport java.util.TreeSet;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        TreeSet<String> tides = new TreeSet<>();\n        for (String time : scanner.nextLine().split(\" \")) {\n            tides.add(time);\n        }\n        while (true) {\n            String time = scanner.nextLine();\n            if (time.equals(\"end\")) {\n                break;\n            }\n            String previous = tides.floor(time);\n            String next = tides.ceiling(time);\n            if (previous == null) {\n                previous = \"none\";\n            }\n            if (next == null) {\n                next = \"none\";\n            }\n            System.out.println(time + \": previous \" + previous + \", next \" + next);\n        }\n    }\n}\n","hints":["Read the first line, split it with `split(\" \")`, and add each time to a `TreeSet<String>`. Times in `HH:MM` form sort correctly as Strings.","`tides.floor(time)` is the latest tide at or before the time, and `tides.ceiling(time)` the earliest at or after it. Each is `null` when there's none: print `none` then."],"tests":[{"name":"Test 1","stdin":"07:30 06:40 08:15 07:05\n07:20\n07:30\n06:00\n09:00\nend\n","expect":"07:20: previous 07:05, next 07:30\n07:30: previous 07:30, next 07:30\n06:00: previous none, next 06:40\n09:00: previous 08:15, next none","hidden":false},{"name":"Test 2","stdin":"12:00\n11:59\n12:00\n12:01\nend\n","expect":"11:59: previous none, next 12:00\n12:00: previous 12:00, next 12:00\n12:01: previous 12:00, next none","hidden":true},{"name":"Test 3","stdin":"23:10 05:45 14:20 09:30 14:20 18:05\n14:19\n14:21\n00:00\n23:59\n10:00\nend\n","expect":"14:19: previous 09:30, next 14:20\n14:21: previous 14:20, next 18:05\n00:00: previous none, next 05:45\n23:59: previous 23:10, next none\n10:00: previous 09:30, next 14:20","hidden":true}],"require":[{"pattern":"new\\s+TreeSet\\s*<","message":"Keep the tide times in a TreeSet."},{"pattern":"\\.floor\\s*\\(","message":"Find the previous tide with `floor`."},{"pattern":"\\.ceiling\\s*\\(","message":"Find the next tide with `ceiling`."}],"forbid":[]},{"task":"A gardener keeps a journal with a note for some days of the year. The program reads lines until `end`. Each line is a date in `yyyy-mm-dd` form, a space, and the note, such as `2024-05-03 planted beans`. The lines aren't in date order, and a later line with the same date replaces that date's note. Keep the notes in a TreeMap from date to note. At the end, print every note in date order, as the date, a colon, a space and the note. Then print `from `, the first date, ` to ` and the last date, using `firstKey` and `lastKey`. With no notes at all, print only `no notes`. With this input:\n\n```input\n2024-05-03 planted beans\n2024-04-20 sowed tomatoes\n2024-06-11 first strawberry\n2024-05-03 planted beans and peas\nend\n```\n\nthe program prints:\n\n```\n2024-04-20: sowed tomatoes\n2024-05-03: planted beans and peas\n2024-06-11: first strawberry\nfrom 2024-04-20 to 2024-06-11\n```\n","kind":"code","seed":"import java.util.Scanner;\nimport java.util.TreeMap;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n\n    }\n}\n","solution":"import java.util.Scanner;\nimport java.util.TreeMap;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        TreeMap<String, String> journal = new TreeMap<>();\n        while (true) {\n            String line = scanner.nextLine();\n            if (line.equals(\"end\")) {\n                break;\n            }\n            journal.put(line.substring(0, 10), line.substring(11));\n        }\n        if (journal.isEmpty()) {\n            System.out.println(\"no notes\");\n            return;\n        }\n        for (var entry : journal.entrySet()) {\n            System.out.println(entry.getKey() + \": \" + entry.getValue());\n        }\n        System.out.println(\"from \" + journal.firstKey() + \" to \" + journal.lastKey());\n    }\n}\n","hints":["A date is the first 10 characters of a line, `line.substring(0, 10)`, and the note is everything after the space, `line.substring(11)`. `put` replaces the note of a date that's already there.","Check `journal.isEmpty()` before calling `firstKey`: on an empty TreeMap it throws a NoSuchElementException. Go through `journal.entrySet()` with `var entry`, as in module 57."],"tests":[{"name":"Test 1","stdin":"2024-05-03 planted beans\n2024-04-20 sowed tomatoes\n2024-06-11 first strawberry\n2024-05-03 planted beans and peas\nend\n","expect":"2024-04-20: sowed tomatoes\n2024-05-03: planted beans and peas\n2024-06-11: first strawberry\nfrom 2024-04-20 to 2024-06-11","hidden":false},{"name":"Test 2","stdin":"end\n","expect":"no notes","hidden":true},{"name":"Test 3","stdin":"2025-01-09 ordered seeds\nend\n","expect":"2025-01-09: ordered seeds\nfrom 2025-01-09 to 2025-01-09","hidden":true},{"name":"Test 4","stdin":"2024-10-02 picked the last apples\n2023-11-30 covered the roses\n2024-02-29 pruned the plum tree\n2024-10-01 dug up potatoes\nend\n","expect":"2023-11-30: covered the roses\n2024-02-29: pruned the plum tree\n2024-10-01: dug up potatoes\n2024-10-02: picked the last apples\nfrom 2023-11-30 to 2024-10-02","hidden":true}],"require":[{"pattern":"new\\s+TreeMap\\s*<","message":"Keep the notes in a TreeMap from date to note."},{"pattern":"\\.firstKey\\s*\\(","message":"Find the first date with `firstKey()`."},{"pattern":"\\.lastKey\\s*\\(","message":"Find the last date with `lastKey()`."}],"forbid":[]}]}