{"id":"list-and-map-internals-fast-lookups","slug":"why-lookups-are-fast","title":"Why lookups stay fast","text":"Module 33 counted the comparisons of an ArrayList's `contains` and a HashMap's `containsKey`, by counting the calls to a key's `equals`. Now that the list and the map are your own, the counting can go inside them. For this experiment only, SimpleList and SimpleMap count every comparison they make, `contains` in the list and `get` in the map. Both classes keep only the methods the experiment needs:\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        int[] sizes = {100, 1000, 10000, 100000};\n        for (int size : sizes) {\n            SimpleList<Integer> list = new SimpleList<>();\n            SimpleMap<Integer, Integer> map = new SimpleMap<>();\n            for (int i = 1; i <= size; i++) {\n                list.add(i);\n                map.put(i, i);\n            }\n            list.contains(size);\n            map.get(size);\n            System.out.println(size + \" values: list \" + list.getComparisons() + \", map \" + map.getComparisons());\n        }\n    }\n}\n// ==== SimpleList.java ====\npublic class SimpleList<T> {\n    private T[] values;\n    private int size;\n    private int comparisons;\n\n    @SuppressWarnings(\"unchecked\")\n    public SimpleList() {\n        this.values = (T[]) new Object[4];\n        this.size = 0;\n        this.comparisons = 0;\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public void add(T value) {\n        if (this.size == this.values.length) {\n            T[] bigger = (T[]) new Object[this.values.length * 2];\n            for (int i = 0; i < this.size; i++) {\n                bigger[i] = this.values[i];\n            }\n            this.values = bigger;\n        }\n        this.values[this.size] = value;\n        this.size++;\n    }\n\n    public boolean contains(T value) {\n        for (int i = 0; i < this.size; i++) {\n            this.comparisons++;\n            if (this.values[i].equals(value)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    public int getComparisons() {\n        return this.comparisons;\n    }\n}\n// ==== SimpleMap.java ====\nimport java.util.ArrayList;\n\npublic class SimpleMap<K, V> {\n    private ArrayList<KeyValue<K, V>>[] buckets;\n    private int size;\n    private int comparisons;\n\n    @SuppressWarnings(\"unchecked\")\n    public SimpleMap() {\n        this.buckets = new ArrayList[8];\n        for (int i = 0; i < this.buckets.length; i++) {\n            this.buckets[i] = new ArrayList<>();\n        }\n        this.size = 0;\n        this.comparisons = 0;\n    }\n\n    private ArrayList<KeyValue<K, V>> bucketFor(K key) {\n        int index = Math.abs(key.hashCode() % this.buckets.length);\n        return this.buckets[index];\n    }\n\n    public void put(K key, V value) {\n        ArrayList<KeyValue<K, V>> bucket = bucketFor(key);\n        for (KeyValue<K, V> pair : bucket) {\n            if (pair.getKey().equals(key)) {\n                pair.setValue(value);\n                return;\n            }\n        }\n        bucket.add(new KeyValue<>(key, value));\n        this.size++;\n        if (this.size > 0.75 * this.buckets.length) {\n            grow();\n        }\n    }\n\n    public V get(K key) {\n        for (KeyValue<K, V> pair : bucketFor(key)) {\n            this.comparisons++;\n            if (pair.getKey().equals(key)) {\n                return pair.getValue();\n            }\n        }\n        return null;\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private void grow() {\n        ArrayList<KeyValue<K, V>>[] old = this.buckets;\n        this.buckets = new ArrayList[old.length * 2];\n        for (int i = 0; i < this.buckets.length; i++) {\n            this.buckets[i] = new ArrayList<>();\n        }\n        for (ArrayList<KeyValue<K, V>> bucket : old) {\n            for (KeyValue<K, V> pair : bucket) {\n                bucketFor(pair.getKey()).add(pair);\n            }\n        }\n    }\n\n    public int getComparisons() {\n        return this.comparisons;\n    }\n}\n// ==== KeyValue.java ====\npublic class KeyValue<K, V> {\n    private K key;\n    private V value;\n\n    public KeyValue(K key, V value) {\n        this.key = key;\n        this.value = value;\n    }\n\n    public K getKey() {\n        return this.key;\n    }\n\n    public V getValue() {\n        return this.value;\n    }\n\n    public void setValue(V value) {\n        this.value = value;\n    }\n}\n```\n\n```output\n100 values: list 100, map 1\n1000 values: list 1000, map 1\n10000 values: list 10000, map 1\n100000 values: list 100000, map 1\n```\n\n- The list's `contains` compared the wanted value with every value before it found it at the end. Ten times the values, ten times the comparisons.\n- The map's `get` went straight to the bucket of its key, and compared once, at every size. The map had grown along with its keys, so that bucket held just that one key.\n- A value that isn't there is the worst case for the list: `contains` compares with every value before it can say `false`. The map looks in one bucket and is done.\n\nComparisons aren't the whole cost. Working out a hash code takes a moment too (a String's goes through all its characters, module 32), but that work depends on the key, not on how many keys the map holds.\n\n### What keeps the map fast\n\nThree things you built work together:\n\n1. **The hash code picks the bucket.** `get` never looks at the other buckets at all.\n2. **Growing keeps the buckets short.** There are never more than 0.75 pairs per bucket on average, however many keys go in. Without `grow`, 100,000 keys in 8 buckets would mean about 12,500 per bucket.\n3. **Good hash codes spread the keys out.** If every key had the same hash code, they'd all share one bucket, and `get` would compare with key after key: a list again, in a slower form. That's why module 32's `hashCode` combined all the values `equals` compares, multiplying by 31 along the way. Java's String and Integer already have hash codes that spread well.\n\nThe map pays for its speed in other ways. It needs more memory: an array of buckets, a list for each bucket and an object for each pair. Now and then a `put` has to grow the map and put every pair again. And its keys come out bucket by bucket, not in the order they went in.\n\n### The two, side by side\n\n| | Your list, and ArrayList | Your map, and HashMap |\n| --- | --- | --- |\n| Adding | at the end, quick; now and then the array is copied into a bigger one | quick; now and then every pair is put again into twice as many buckets |\n| By position | `get(index)` goes straight to the place in the array | no positions |\n| Finding | `contains` and `indexOf` compare with one element after another | `get` and `containsKey` look in one short bucket |\n| Removing | moves every element after the removed one | removes from one short bucket |\n| Order | the order of adding | bucket by bucket |\n\nJava's own ArrayList and HashMap are built on these same ideas, with refinements. HashMap starts with 16 buckets by default. In OpenJDK, ArrayList's array grows by half its length rather than doubling, and HashMap mixes the bits of each hash code before it picks a bucket, so that keys spread out better, and turns a bucket that gets very long into a tree, which is quicker to search. These are implementation details, not promises: another Java could do them differently. You'll go on using Java's versions; now you know what they do when you call `add`, `get` and `put`.\n","task":"This time count the comparisons yourself. `SimpleList` and `SimpleMap` are the counting versions from this step, except that this map has no `grow`: it keeps its 8 buckets. An Integer's hash code is its own value. The counts add up: each `getComparisons()` gives every comparison made so far. What does this program print?\n","kind":"predict","seed":"public class Main {\n    public static void main(String[] args) {\n        SimpleList<String> trees = new SimpleList<>();\n        String[] names = {\"oak\", \"elm\", \"ash\", \"fir\", \"yew\"};\n        for (String name : names) {\n            trees.add(name);\n        }\n        trees.contains(\"ash\");\n        System.out.println(trees.getComparisons());\n        trees.contains(\"pine\");\n        System.out.println(trees.getComparisons());\n\n        SimpleMap<Integer, String> plots = new SimpleMap<>();\n        plots.put(3, \"beans\");\n        plots.put(11, \"kale\");\n        plots.put(19, \"leeks\");\n        plots.put(4, \"chard\");\n        plots.get(19);\n        System.out.println(plots.getComparisons());\n        plots.get(27);\n        plots.get(5);\n        System.out.println(plots.getComparisons());\n    }\n}\n// ==== SimpleList.java ====\npublic class SimpleList<T> {\n    private T[] values;\n    private int size;\n    private int comparisons;\n\n    @SuppressWarnings(\"unchecked\")\n    public SimpleList() {\n        this.values = (T[]) new Object[4];\n        this.size = 0;\n        this.comparisons = 0;\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public void add(T value) {\n        if (this.size == this.values.length) {\n            T[] bigger = (T[]) new Object[this.values.length * 2];\n            for (int i = 0; i < this.size; i++) {\n                bigger[i] = this.values[i];\n            }\n            this.values = bigger;\n        }\n        this.values[this.size] = value;\n        this.size++;\n    }\n\n    public boolean contains(T value) {\n        for (int i = 0; i < this.size; i++) {\n            this.comparisons++;\n            if (this.values[i].equals(value)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    public int getComparisons() {\n        return this.comparisons;\n    }\n}\n// ==== SimpleMap.java ====\nimport java.util.ArrayList;\n\npublic class SimpleMap<K, V> {\n    private ArrayList<KeyValue<K, V>>[] buckets;\n    private int size;\n    private int comparisons;\n\n    @SuppressWarnings(\"unchecked\")\n    public SimpleMap() {\n        this.buckets = new ArrayList[8];\n        for (int i = 0; i < this.buckets.length; i++) {\n            this.buckets[i] = new ArrayList<>();\n        }\n        this.size = 0;\n        this.comparisons = 0;\n    }\n\n    private ArrayList<KeyValue<K, V>> bucketFor(K key) {\n        int index = Math.abs(key.hashCode() % this.buckets.length);\n        return this.buckets[index];\n    }\n\n    public void put(K key, V value) {\n        ArrayList<KeyValue<K, V>> bucket = bucketFor(key);\n        for (KeyValue<K, V> pair : bucket) {\n            if (pair.getKey().equals(key)) {\n                pair.setValue(value);\n                return;\n            }\n        }\n        bucket.add(new KeyValue<>(key, value));\n        this.size++;\n    }\n\n    public V get(K key) {\n        for (KeyValue<K, V> pair : bucketFor(key)) {\n            this.comparisons++;\n            if (pair.getKey().equals(key)) {\n                return pair.getValue();\n            }\n        }\n        return null;\n    }\n\n    public int getComparisons() {\n        return this.comparisons;\n    }\n}\n// ==== KeyValue.java ====\npublic class KeyValue<K, V> {\n    private K key;\n    private V value;\n\n    public KeyValue(K key, V value) {\n        this.key = key;\n        this.value = value;\n    }\n\n    public K getKey() {\n        return this.key;\n    }\n\n    public V getValue() {\n        return this.value;\n    }\n\n    public void setValue(V value) {\n        this.value = value;\n    }\n}\n","solution":"public class Main {\n    public static void main(String[] args) {\n        SimpleList<String> trees = new SimpleList<>();\n        String[] names = {\"oak\", \"elm\", \"ash\", \"fir\", \"yew\"};\n        for (String name : names) {\n            trees.add(name);\n        }\n        trees.contains(\"ash\");\n        System.out.println(trees.getComparisons());\n        trees.contains(\"pine\");\n        System.out.println(trees.getComparisons());\n\n        SimpleMap<Integer, String> plots = new SimpleMap<>();\n        plots.put(3, \"beans\");\n        plots.put(11, \"kale\");\n        plots.put(19, \"leeks\");\n        plots.put(4, \"chard\");\n        plots.get(19);\n        System.out.println(plots.getComparisons());\n        plots.get(27);\n        plots.get(5);\n        System.out.println(plots.getComparisons());\n    }\n}\n// ==== SimpleList.java ====\npublic class SimpleList<T> {\n    private T[] values;\n    private int size;\n    private int comparisons;\n\n    @SuppressWarnings(\"unchecked\")\n    public SimpleList() {\n        this.values = (T[]) new Object[4];\n        this.size = 0;\n        this.comparisons = 0;\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public void add(T value) {\n        if (this.size == this.values.length) {\n            T[] bigger = (T[]) new Object[this.values.length * 2];\n            for (int i = 0; i < this.size; i++) {\n                bigger[i] = this.values[i];\n            }\n            this.values = bigger;\n        }\n        this.values[this.size] = value;\n        this.size++;\n    }\n\n    public boolean contains(T value) {\n        for (int i = 0; i < this.size; i++) {\n            this.comparisons++;\n            if (this.values[i].equals(value)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    public int getComparisons() {\n        return this.comparisons;\n    }\n}\n// ==== SimpleMap.java ====\nimport java.util.ArrayList;\n\npublic class SimpleMap<K, V> {\n    private ArrayList<KeyValue<K, V>>[] buckets;\n    private int size;\n    private int comparisons;\n\n    @SuppressWarnings(\"unchecked\")\n    public SimpleMap() {\n        this.buckets = new ArrayList[8];\n        for (int i = 0; i < this.buckets.length; i++) {\n            this.buckets[i] = new ArrayList<>();\n        }\n        this.size = 0;\n        this.comparisons = 0;\n    }\n\n    private ArrayList<KeyValue<K, V>> bucketFor(K key) {\n        int index = Math.abs(key.hashCode() % this.buckets.length);\n        return this.buckets[index];\n    }\n\n    public void put(K key, V value) {\n        ArrayList<KeyValue<K, V>> bucket = bucketFor(key);\n        for (KeyValue<K, V> pair : bucket) {\n            if (pair.getKey().equals(key)) {\n                pair.setValue(value);\n                return;\n            }\n        }\n        bucket.add(new KeyValue<>(key, value));\n        this.size++;\n    }\n\n    public V get(K key) {\n        for (KeyValue<K, V> pair : bucketFor(key)) {\n            this.comparisons++;\n            if (pair.getKey().equals(key)) {\n                return pair.getValue();\n            }\n        }\n        return null;\n    }\n\n    public int getComparisons() {\n        return this.comparisons;\n    }\n}\n// ==== KeyValue.java ====\npublic class KeyValue<K, V> {\n    private K key;\n    private V value;\n\n    public KeyValue(K key, V value) {\n        this.key = key;\n        this.value = value;\n    }\n\n    public K getKey() {\n        return this.key;\n    }\n\n    public V getValue() {\n        return this.value;\n    }\n\n    public void setValue(V value) {\n        this.value = value;\n    }\n}\n","hints":["The list compares from the start: ash is the third value, so finding it takes 3 comparisons. Pine isn't there, so `contains` compares with all 5 values. The count goes on from 3.","Only `get` counts in the map. 3, 11 and 19 all go to bucket 3, in that order, so `get(19)` compares 3 times. 27 goes to bucket 3 too, and isn't there. 5 goes to bucket 5, which is empty: no comparisons at all."],"tests":[{"name":"Output","stdin":"","expect":"3\n8\n3\n6","hidden":false}],"require":[],"forbid":[],"lines":["3","8","3","6"],"more":[{"task":"A seed library keeps its seed packets in drawers. A drawer is named by its cabinet, a letter, and its number, like C-117, and the library finds packets with a SimpleMap from Drawer to packet. `Drawer.java` has an `equals` that compares both, but its `hashCode` uses only the cabinet, so all the drawers of a cabinet share one bucket, however many buckets the map has.\n\n`SimpleMap.java` is the counting map from this step. `Main.java` puts the 300 drawers of cabinet C in the map, finds each of them once, and prints `fast` when that took fewer than 600 comparisons, or `slow` and the count. Right now it prints `slow: 45150 comparisons`. Write a better `hashCode` in `Drawer.java`, one that uses both the cabinet and the number, so that equal drawers still get equal hash codes. Then the program prints:\n\n```\nfast\nC-117: tomatoes\n```\n","kind":"code","seed":"// ==== Drawer.java ====\npublic class Drawer {\n    private String cabinet;\n    private int number;\n\n    public Drawer(String cabinet, int number) {\n        this.cabinet = cabinet;\n        this.number = number;\n    }\n\n    @Override\n    public boolean equals(Object compared) {\n        if (this == compared) {\n            return true;\n        }\n        if (!(compared instanceof Drawer)) {\n            return false;\n        }\n        Drawer other = (Drawer) compared;\n        return this.cabinet.equals(other.cabinet) && this.number == other.number;\n    }\n\n    @Override\n    public int hashCode() {\n        return this.cabinet.hashCode();\n    }\n\n    @Override\n    public String toString() {\n        return this.cabinet + \"-\" + this.number;\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        SimpleMap<Drawer, String> packets = new SimpleMap<>();\n        for (int i = 1; i <= 300; i++) {\n            packets.put(new Drawer(\"C\", i), \"packet \" + i);\n        }\n        packets.put(new Drawer(\"C\", 117), \"tomatoes\");\n        for (int i = 1; i <= 300; i++) {\n            packets.get(new Drawer(\"C\", i));\n        }\n        if (packets.getComparisons() < 600) {\n            System.out.println(\"fast\");\n        } else {\n            System.out.println(\"slow: \" + packets.getComparisons() + \" comparisons\");\n        }\n        Drawer wanted = new Drawer(\"C\", 117);\n        System.out.println(wanted + \": \" + packets.get(wanted));\n    }\n}\n\n// ==== SimpleMap.java ====\nimport java.util.ArrayList;\n\npublic class SimpleMap<K, V> {\n    private ArrayList<KeyValue<K, V>>[] buckets;\n    private int size;\n    private int comparisons;\n\n    @SuppressWarnings(\"unchecked\")\n    public SimpleMap() {\n        this.buckets = new ArrayList[8];\n        for (int i = 0; i < this.buckets.length; i++) {\n            this.buckets[i] = new ArrayList<>();\n        }\n        this.size = 0;\n        this.comparisons = 0;\n    }\n\n    private ArrayList<KeyValue<K, V>> bucketFor(K key) {\n        int index = Math.abs(key.hashCode() % this.buckets.length);\n        return this.buckets[index];\n    }\n\n    public void put(K key, V value) {\n        ArrayList<KeyValue<K, V>> bucket = bucketFor(key);\n        for (KeyValue<K, V> pair : bucket) {\n            if (pair.getKey().equals(key)) {\n                pair.setValue(value);\n                return;\n            }\n        }\n        bucket.add(new KeyValue<>(key, value));\n        this.size++;\n        if (this.size > 0.75 * this.buckets.length) {\n            grow();\n        }\n    }\n\n    public V get(K key) {\n        for (KeyValue<K, V> pair : bucketFor(key)) {\n            this.comparisons++;\n            if (pair.getKey().equals(key)) {\n                return pair.getValue();\n            }\n        }\n        return null;\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private void grow() {\n        ArrayList<KeyValue<K, V>>[] old = this.buckets;\n        this.buckets = new ArrayList[old.length * 2];\n        for (int i = 0; i < this.buckets.length; i++) {\n            this.buckets[i] = new ArrayList<>();\n        }\n        for (ArrayList<KeyValue<K, V>> bucket : old) {\n            for (KeyValue<K, V> pair : bucket) {\n                bucketFor(pair.getKey()).add(pair);\n            }\n        }\n    }\n\n    public int getComparisons() {\n        return this.comparisons;\n    }\n}\n\n// ==== KeyValue.java ====\npublic class KeyValue<K, V> {\n    private K key;\n    private V value;\n\n    public KeyValue(K key, V value) {\n        this.key = key;\n        this.value = value;\n    }\n\n    public K getKey() {\n        return this.key;\n    }\n\n    public V getValue() {\n        return this.value;\n    }\n\n    public void setValue(V value) {\n        this.value = value;\n    }\n}\n","solution":"// ==== Drawer.java ====\npublic class Drawer {\n    private String cabinet;\n    private int number;\n\n    public Drawer(String cabinet, int number) {\n        this.cabinet = cabinet;\n        this.number = number;\n    }\n\n    @Override\n    public boolean equals(Object compared) {\n        if (this == compared) {\n            return true;\n        }\n        if (!(compared instanceof Drawer)) {\n            return false;\n        }\n        Drawer other = (Drawer) compared;\n        return this.cabinet.equals(other.cabinet) && this.number == other.number;\n    }\n\n    @Override\n    public int hashCode() {\n        return 31 * this.cabinet.hashCode() + this.number;\n    }\n\n    @Override\n    public String toString() {\n        return this.cabinet + \"-\" + this.number;\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        SimpleMap<Drawer, String> packets = new SimpleMap<>();\n        for (int i = 1; i <= 300; i++) {\n            packets.put(new Drawer(\"C\", i), \"packet \" + i);\n        }\n        packets.put(new Drawer(\"C\", 117), \"tomatoes\");\n        for (int i = 1; i <= 300; i++) {\n            packets.get(new Drawer(\"C\", i));\n        }\n        if (packets.getComparisons() < 600) {\n            System.out.println(\"fast\");\n        } else {\n            System.out.println(\"slow: \" + packets.getComparisons() + \" comparisons\");\n        }\n        Drawer wanted = new Drawer(\"C\", 117);\n        System.out.println(wanted + \": \" + packets.get(wanted));\n    }\n}\n\n// ==== SimpleMap.java ====\nimport java.util.ArrayList;\n\npublic class SimpleMap<K, V> {\n    private ArrayList<KeyValue<K, V>>[] buckets;\n    private int size;\n    private int comparisons;\n\n    @SuppressWarnings(\"unchecked\")\n    public SimpleMap() {\n        this.buckets = new ArrayList[8];\n        for (int i = 0; i < this.buckets.length; i++) {\n            this.buckets[i] = new ArrayList<>();\n        }\n        this.size = 0;\n        this.comparisons = 0;\n    }\n\n    private ArrayList<KeyValue<K, V>> bucketFor(K key) {\n        int index = Math.abs(key.hashCode() % this.buckets.length);\n        return this.buckets[index];\n    }\n\n    public void put(K key, V value) {\n        ArrayList<KeyValue<K, V>> bucket = bucketFor(key);\n        for (KeyValue<K, V> pair : bucket) {\n            if (pair.getKey().equals(key)) {\n                pair.setValue(value);\n                return;\n            }\n        }\n        bucket.add(new KeyValue<>(key, value));\n        this.size++;\n        if (this.size > 0.75 * this.buckets.length) {\n            grow();\n        }\n    }\n\n    public V get(K key) {\n        for (KeyValue<K, V> pair : bucketFor(key)) {\n            this.comparisons++;\n            if (pair.getKey().equals(key)) {\n                return pair.getValue();\n            }\n        }\n        return null;\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private void grow() {\n        ArrayList<KeyValue<K, V>>[] old = this.buckets;\n        this.buckets = new ArrayList[old.length * 2];\n        for (int i = 0; i < this.buckets.length; i++) {\n            this.buckets[i] = new ArrayList<>();\n        }\n        for (ArrayList<KeyValue<K, V>> bucket : old) {\n            for (KeyValue<K, V> pair : bucket) {\n                bucketFor(pair.getKey()).add(pair);\n            }\n        }\n    }\n\n    public int getComparisons() {\n        return this.comparisons;\n    }\n}\n\n// ==== KeyValue.java ====\npublic class KeyValue<K, V> {\n    private K key;\n    private V value;\n\n    public KeyValue(K key, V value) {\n        this.key = key;\n        this.value = value;\n    }\n\n    public K getKey() {\n        return this.key;\n    }\n\n    public V getValue() {\n        return this.value;\n    }\n\n    public void setValue(V value) {\n        this.value = value;\n    }\n}\n","hints":["Combine both values the way module 32's Seat did, using exactly the two values that `equals` compares.","`return 31 * this.cabinet.hashCode() + this.number;` (or `Objects.hash(this.cabinet, this.number)`, with `import java.util.Objects;`). Just adding the two isn't enough: then A-12 and B-11 would share a hash code, since B's code is one more than A's. And the hash code must not depend on anything `equals` doesn't compare, or equal drawers could get different hash codes."],"tests":[{"name":"The program as given","stdin":"","expect":"fast\nC-117: tomatoes","hidden":false},{"name":"Test 2","stdin":"","call":"SimpleMap<Drawer, Integer> map = new SimpleMap<>();\nString[] cabinets = {\"A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"G\", \"H\", \"I\", \"J\"};\nfor (String cabinet : cabinets) {\n    for (int i = 1; i <= 30; i++) {\n        map.put(new Drawer(cabinet, i), i);\n    }\n}\nint found = 0;\nfor (String cabinet : cabinets) {\n    for (int i = 1; i <= 30; i++) {\n        if (map.get(new Drawer(cabinet, i)) == i) {\n            found++;\n        }\n    }\n}\nSystem.out.println(found);\nSystem.out.println(map.getComparisons() < 600 ? \"fast\" : \"slow\");\n","expect":"300\nfast","hidden":true},{"name":"Test 3","stdin":"","call":"Drawer one = new Drawer(\"B\", 12);\nDrawer same = new Drawer(\"B\", 12);\nSystem.out.println(one.hashCode() == same.hashCode());\nSystem.out.println(one.equals(new Drawer(\"B\", 21)) + \" \" + one.equals(new Drawer(\"C\", 12)));\nSimpleMap<Drawer, String> map = new SimpleMap<>();\nmap.put(one, \"basil\");\nmap.put(same, \"thyme\");\nSystem.out.println(map.get(new Drawer(\"B\", 12)) + \" \" + map.get(new Drawer(\"B\", 13)));\n","expect":"true\nfalse false\nthyme null","hidden":true}],"require":[],"forbid":[]},{"task":"To end the module: Java's HashSet keeps each value once, and finds it fast. Build your own, the way SimpleMap works, only with values and no pairs. A word game uses it for the words already played: each word may be played only once.\n\nIn `SimpleSet.java`, write the generic class `SimpleSet<T>`, with an array of buckets that are `ArrayList<T>` lists:\n\n- a constructor with no parameters: 8 buckets, each an empty list\n- `add(T value)`: if the set has the value already, it returns `false` and changes nothing. Otherwise it puts the value in its bucket and returns `true`. After adding, when the size is more than 0.75 times the number of buckets, the buckets become twice as many, with every value put again in its new bucket.\n- `contains(T value)`, which returns whether the set has the value\n- `remove(T value)`, which removes the value and returns `true`, or returns `false` when the set doesn't have it\n- `size()`, the number of values in the set\n- `bucketCount()`, the number of buckets, so that the check can watch the set grow\n\nPick a value's bucket as SimpleMap does, with its hash code. The check runs:\n\n```java\nSimpleSet<String> played = new SimpleSet<>();\nSystem.out.println(played.add(\"maple\") + \" \" + played.add(\"otter\") + \" \" + played.add(\"maple\"));\nSystem.out.println(played.contains(\"otter\") + \" \" + played.contains(\"cedar\") + \" \" + played.size());\n```\n\nand expects:\n\n```\ntrue true false\ntrue false 2\n```\n","kind":"code","seed":"// ==== SimpleSet.java ====\nimport java.util.ArrayList;\n\npublic class SimpleSet<T> {\n\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        SimpleSet<String> played = new SimpleSet<>();\n        System.out.println(played.add(\"maple\") + \" \" + played.add(\"otter\") + \" \" + played.add(\"maple\"));\n        System.out.println(played.contains(\"otter\") + \" \" + played.contains(\"cedar\") + \" \" + played.size());\n    }\n}\n","solution":"// ==== SimpleSet.java ====\nimport java.util.ArrayList;\n\npublic class SimpleSet<T> {\n    private ArrayList<T>[] buckets;\n    private int size;\n\n    @SuppressWarnings(\"unchecked\")\n    public SimpleSet() {\n        this.buckets = new ArrayList[8];\n        for (int i = 0; i < this.buckets.length; i++) {\n            this.buckets[i] = new ArrayList<>();\n        }\n        this.size = 0;\n    }\n\n    private ArrayList<T> bucketFor(T value) {\n        int index = Math.abs(value.hashCode() % this.buckets.length);\n        return this.buckets[index];\n    }\n\n    public boolean add(T value) {\n        ArrayList<T> bucket = bucketFor(value);\n        if (bucket.contains(value)) {\n            return false;\n        }\n        bucket.add(value);\n        this.size++;\n        if (this.size > 0.75 * this.buckets.length) {\n            grow();\n        }\n        return true;\n    }\n\n    public boolean contains(T value) {\n        return bucketFor(value).contains(value);\n    }\n\n    public boolean remove(T value) {\n        if (bucketFor(value).remove(value)) {\n            this.size--;\n            return true;\n        }\n        return false;\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private void grow() {\n        ArrayList<T>[] old = this.buckets;\n        this.buckets = new ArrayList[old.length * 2];\n        for (int i = 0; i < this.buckets.length; i++) {\n            this.buckets[i] = new ArrayList<>();\n        }\n        for (ArrayList<T> bucket : old) {\n            for (T value : bucket) {\n                bucketFor(value).add(value);\n            }\n        }\n    }\n\n    public int size() {\n        return this.size;\n    }\n\n    public int bucketCount() {\n        return this.buckets.length;\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        SimpleSet<String> played = new SimpleSet<>();\n        System.out.println(played.add(\"maple\") + \" \" + played.add(\"otter\") + \" \" + played.add(\"maple\"));\n        System.out.println(played.contains(\"otter\") + \" \" + played.contains(\"cedar\") + \" \" + played.size());\n    }\n}\n","hints":["Start from SimpleMap: `private ArrayList<T>[] buckets;` and a size, the same constructor (with `new ArrayList[8]`), and a `bucketFor(T value)` that picks the bucket from `value.hashCode()`. A bucket is a short ArrayList, so its own `contains` and `remove` can do the searching inside it.","`add` returns `false` when `bucketFor(value).contains(value)`. Otherwise it adds, counts, grows when `this.size > 0.75 * this.buckets.length`, and returns `true`. `grow` is SimpleMap's, with values instead of pairs. In `remove`, the bucket's `remove(value)` returns whether it removed something."],"tests":[{"name":"Test 1","stdin":"","call":"SimpleSet<String> played = new SimpleSet<>();\nSystem.out.println(played.add(\"maple\") + \" \" + played.add(\"otter\") + \" \" + played.add(\"maple\"));\nSystem.out.println(played.contains(\"otter\") + \" \" + played.contains(\"cedar\") + \" \" + played.size());\n","expect":"true true false\ntrue false 2","hidden":false},{"name":"Test 2","stdin":"","call":"SimpleSet<Integer> numbers = new SimpleSet<>();\nint added = 0;\nfor (int i = 0; i < 150; i++) {\n    if (numbers.add(i % 100 * 7)) {\n        added++;\n    }\n}\nSystem.out.println(added + \" \" + numbers.size() + \" \" + numbers.bucketCount());\nint removed = 0;\nfor (int i = 0; i < 100; i += 2) {\n    if (numbers.remove(i * 7)) {\n        removed++;\n    }\n}\nSystem.out.println(removed + \" \" + numbers.size() + \" \" + numbers.remove(14) + \" \" + numbers.remove(5));\nSystem.out.println(numbers.contains(7) + \" \" + numbers.contains(14) + \" \" + numbers.contains(693) + \" \" + numbers.add(14) + \" \" + numbers.size());\n","expect":"100 100 256\n50 50 false false\ntrue false true true 51","hidden":true},{"name":"Test 3","stdin":"","call":"SimpleSet<String> words = new SimpleSet<>();\nSystem.out.println(words.size() + \" \" + words.bucketCount() + \" \" + words.contains(\"Aa\") + \" \" + words.remove(\"Aa\"));\nString[] all = {\"Aa\", \"BB\", \"Oakdale\", \"uno\", \"dos\", \"tres\", \"cuatro\"};\nfor (String word : all) {\n    words.add(word);\n}\nString start = \"B\";\nSystem.out.println(words.size() + \" \" + words.bucketCount() + \" \" + words.contains(start + \"B\") + \" \" + words.add(\"Aa\"));\nSystem.out.println(words.remove(\"BB\") + \" \" + words.contains(\"BB\") + \" \" + words.contains(\"Aa\") + \" \" + words.contains(\"Oakdale\") + \" \" + words.size());\n","expect":"0 8 false false\n7 16 true false\ntrue false true true 6","hidden":true}],"require":[{"pattern":"\\bArrayList\\s*<\\s*T\\s*>\\s*\\[\\s*\\]","message":"Keep the values in an array of buckets, `ArrayList<T>[]`, as SimpleMap keeps its pairs."},{"pattern":"\\.\\s*hashCode\\s*\\(","message":"Pick each value's bucket from its hash code, `value.hashCode()`, as SimpleMap does."}],"forbid":[{"pattern":"\\b(HashSet|HashMap|TreeSet|TreeMap|Set|Map)\\b","message":"Build the set from your own buckets, without Java's sets and maps."}]}]}