{"id":"list-and-map-internals-remove","slug":"remove","title":"Removing, and moving the rest along","text":"When an ArrayList removes an element, it closes the gap: every element after it moves one place toward the front, and its index goes down by one. Your own list has to do that work itself.\n\n### Why not just null?\n\nSetting the removed place to `null` would leave a hole in the middle of the list. The songs after it would keep their old indexes, `get` would return `null` from the hole, and `indexOf` would crash on it. A list has no holes: its songs are always at the indexes 0 to `size - 1`.\n\nSo the songs after the removed one each move one place to the left. Here the song at index 1 (B) is removed from a list of five, in an array of 8 (a dash is an empty place):\n\n```text\nindex:     0  1  2  3  4  5\nbefore:    A  B  C  D  E  -    size 5\nmove C:    A  C  C  D  E  -\nmove D:    A  C  D  D  E  -\nmove E:    A  C  D  E  E  -\nclear 4:   A  C  D  E  -  -    size 4\n```\n\nEach move copies a song from the next place, from the front to the back. After the last move, the old last place still holds a copy of E, so it's set to `null` before the size goes down. Nothing reads the places after the size anyway, but a leftover reference there could keep an object in memory after the list has let go of it: remove the last song, and the reference in its place would be the only one left. ArrayList clears that place too.\n\n### remove in code\n\nLike ArrayList, Playlist gets two `remove` methods: one by index, which returns the removed song, and one by value, which returns whether it found the song:\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        Playlist playlist = new Playlist();\n        playlist.add(\"Morning Rain\");\n        playlist.add(\"Night Train\");\n        playlist.add(\"Paper Boats\");\n        playlist.add(\"Slow River\");\n        playlist.add(\"Copper Sky\");\n        System.out.println(playlist);\n\n        String removed = playlist.remove(1);\n        System.out.println(\"Removed \" + removed + \": \" + playlist);\n        System.out.println(playlist.remove(\"Copper Sky\") + \" \" + playlist.remove(\"Lantern Light\"));\n        System.out.println(playlist + \", size \" + playlist.size());\n    }\n}\n// ==== Playlist.java ====\npublic class Playlist {\n    private String[] songs;\n    private int size;\n\n    public Playlist() {\n        this.songs = new String[4];\n        this.size = 0;\n    }\n\n    public void add(String song) {\n        if (this.size == this.songs.length) {\n            grow();\n        }\n        this.songs[this.size] = song;\n        this.size++;\n    }\n\n    private void grow() {\n        String[] bigger = new String[this.songs.length * 2];\n        for (int i = 0; i < this.size; i++) {\n            bigger[i] = this.songs[i];\n        }\n        this.songs = bigger;\n    }\n\n    public int size() {\n        return this.size;\n    }\n\n    public String get(int index) {\n        if (index < 0 || index >= this.size) {\n            throw new IndexOutOfBoundsException(\"Index \" + index + \" out of bounds for size \" + this.size);\n        }\n        return this.songs[index];\n    }\n\n    public int indexOf(String song) {\n        for (int i = 0; i < this.size; i++) {\n            if (this.songs[i].equals(song)) {\n                return i;\n            }\n        }\n        return -1;\n    }\n\n    public String remove(int index) {\n        String removed = get(index);\n        for (int i = index; i < this.size - 1; i++) {\n            this.songs[i] = this.songs[i + 1];\n        }\n        this.songs[this.size - 1] = null;\n        this.size--;\n        return removed;\n    }\n\n    public boolean remove(String song) {\n        int index = indexOf(song);\n        if (index < 0) {\n            return false;\n        }\n        remove(index);\n        return true;\n    }\n\n    @Override\n    public String toString() {\n        String text = \"[\";\n        for (int i = 0; i < this.size; i++) {\n            if (i > 0) {\n                text += \", \";\n            }\n            text += this.songs[i];\n        }\n        return text + \"]\";\n    }\n}\n```\n\n```output\n[Morning Rain, Night Train, Paper Boats, Slow River, Copper Sky]\nRemoved Night Train: [Morning Rain, Paper Boats, Slow River, Copper Sky]\ntrue false\n[Morning Rain, Paper Boats, Slow River], size 3\n```\n\n- `remove(int index)` calls `get(index)` first. That checks the index, throwing for a bad one, and it gives the song to return.\n- The loop runs from the removed place up to the second-to-last song: each place takes the song from the place after it. The order matters. Going from the back to the front, E would be copied into D's place, then from there into C's, and so on, and every place would end up holding E.\n- `remove(String song)` lets `indexOf` find the song, and then the other `remove` does the moving. Which of the two Java calls depends on the argument: a String goes to `remove(String song)`, an int to `remove(int index)`, just as with an ArrayList (module 14).\n\n### Long lists, long waits\n\nRemoving at index `i` from a list of `n` songs moves the `n - 1 - i` songs after it. Removing the last song moves nothing; removing the first moves all the others. To see it, this smaller Playlist, with only what the experiment needs, counts its moves:\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        int[] sizes = {10, 1000, 100000};\n        for (int size : sizes) {\n            Playlist playlist = new Playlist();\n            for (int i = 1; i <= size; i++) {\n                playlist.add(\"Song \" + i);\n            }\n            playlist.remove(0);\n            int first = playlist.getMoves();\n            playlist.remove(playlist.size() - 1);\n            int last = playlist.getMoves() - first;\n            System.out.println(size + \" songs: removing the first moved \" + first + \", removing the last moved \" + last);\n        }\n    }\n}\n// ==== Playlist.java ====\npublic class Playlist {\n    private String[] songs;\n    private int size;\n    private int moves;\n\n    public Playlist() {\n        this.songs = new String[4];\n        this.size = 0;\n        this.moves = 0;\n    }\n\n    public void add(String song) {\n        if (this.size == this.songs.length) {\n            String[] bigger = new String[this.songs.length * 2];\n            for (int i = 0; i < this.size; i++) {\n                bigger[i] = this.songs[i];\n            }\n            this.songs = bigger;\n        }\n        this.songs[this.size] = song;\n        this.size++;\n    }\n\n    public int size() {\n        return this.size;\n    }\n\n    public int getMoves() {\n        return this.moves;\n    }\n\n    public String remove(int index) {\n        String removed = this.songs[index];\n        for (int i = index; i < this.size - 1; i++) {\n            this.songs[i] = this.songs[i + 1];\n            this.moves++;\n        }\n        this.songs[this.size - 1] = null;\n        this.size--;\n        return removed;\n    }\n}\n```\n\n```output\n10 songs: removing the first moved 9, removing the last moved 0\n1000 songs: removing the first moved 999, removing the last moved 0\n100000 songs: removing the first moved 99999, removing the last moved 0\n```\n\nRemoving from the middle moves about half the list. Java's ArrayList moves its elements the same way. It uses a fast copying method of Java's own, but the work still grows with the number of elements after the gap. So a program that keeps removing the first element of a long ArrayList, like a line where people leave from the front, does a lot of moving; removing at the end costs nothing extra. Adding at the end moves nothing either (apart from the occasional `grow`), but adding in the middle, with ArrayList's `add(index, value)`, moves everything after it one place the other way. The last challenge of this step builds that.\n","task":"A road trip planner keeps its stops in a list of its own. Fill in the three blanks of `remove` in `Itinerary.java`: how far the loop goes, the index each place takes its stop from, and the last statement, which updates the size. The program prints:\n\n```\nTurin is off the trip\nRome is off the trip\n[Lyon, Genoa, Pisa, Siena], 4 stops\n```\n","kind":"fill","seed":"public class Main {\n    public static void main(String[] args) {\n        Itinerary trip = new Itinerary();\n        trip.add(\"Lyon\");\n        trip.add(\"Turin\");\n        trip.add(\"Genoa\");\n        trip.add(\"Pisa\");\n        System.out.println(trip.remove(1) + \" is off the trip\");\n        trip.add(\"Siena\");\n        trip.add(\"Rome\");\n        System.out.println(trip.remove(4) + \" is off the trip\");\n        System.out.println(trip + \", \" + trip.size() + \" stops\");\n    }\n}\n// ==== Itinerary.java ====\npublic class Itinerary {\n    private String[] stops;\n    private int size;\n\n    public Itinerary() {\n        this.stops = new String[4];\n        this.size = 0;\n    }\n\n    public void add(String stop) {\n        if (this.size == this.stops.length) {\n            String[] bigger = new String[this.stops.length * 2];\n            for (int i = 0; i < this.size; i++) {\n                bigger[i] = this.stops[i];\n            }\n            this.stops = bigger;\n        }\n        this.stops[this.size] = stop;\n        this.size++;\n    }\n\n    public int size() {\n        return this.size;\n    }\n\n    public String remove(int index) {\n        if (index < 0 || index >= this.size) {\n            throw new IndexOutOfBoundsException(\"Index \" + index + \" out of bounds for size \" + this.size);\n        }\n        String removed = this.stops[index];\n        for (int i = index; i < [[this.size - 1‖size - 1]]; i++) {\n            this.stops[i] = this.stops[[[i + 1]]];\n        }\n        this.stops[this.size - 1] = null;\n        [[this.size--;‖size--;‖this.size -= 1;‖this.size = this.size - 1;]]\n        return removed;\n    }\n\n    @Override\n    public String toString() {\n        String text = \"[\";\n        for (int i = 0; i < this.size; i++) {\n            if (i > 0) {\n                text += \", \";\n            }\n            text += this.stops[i];\n        }\n        return text + \"]\";\n    }\n}\n","solution":"public class Main {\n    public static void main(String[] args) {\n        Itinerary trip = new Itinerary();\n        trip.add(\"Lyon\");\n        trip.add(\"Turin\");\n        trip.add(\"Genoa\");\n        trip.add(\"Pisa\");\n        System.out.println(trip.remove(1) + \" is off the trip\");\n        trip.add(\"Siena\");\n        trip.add(\"Rome\");\n        System.out.println(trip.remove(4) + \" is off the trip\");\n        System.out.println(trip + \", \" + trip.size() + \" stops\");\n    }\n}\n// ==== Itinerary.java ====\npublic class Itinerary {\n    private String[] stops;\n    private int size;\n\n    public Itinerary() {\n        this.stops = new String[4];\n        this.size = 0;\n    }\n\n    public void add(String stop) {\n        if (this.size == this.stops.length) {\n            String[] bigger = new String[this.stops.length * 2];\n            for (int i = 0; i < this.size; i++) {\n                bigger[i] = this.stops[i];\n            }\n            this.stops = bigger;\n        }\n        this.stops[this.size] = stop;\n        this.size++;\n    }\n\n    public int size() {\n        return this.size;\n    }\n\n    public String remove(int index) {\n        if (index < 0 || index >= this.size) {\n            throw new IndexOutOfBoundsException(\"Index \" + index + \" out of bounds for size \" + this.size);\n        }\n        String removed = this.stops[index];\n        for (int i = index; i < this.size - 1; i++) {\n            this.stops[i] = this.stops[i + 1];\n        }\n        this.stops[this.size - 1] = null;\n        this.size--;\n        return removed;\n    }\n\n    @Override\n    public String toString() {\n        String text = \"[\";\n        for (int i = 0; i < this.size; i++) {\n            if (i > 0) {\n                text += \", \";\n            }\n            text += this.stops[i];\n        }\n        return text + \"]\";\n    }\n}\n","hints":["Each place takes the stop from the place after it. The last place that takes a stop is the second-to-last one: one round more would read past the last stop, outside a full array. And at the end the list has one stop fewer.","The loop goes while `i < this.size - 1`, and each round reads `this.stops[i + 1]`. With `i < this.size`, the last round would read `this.stops[this.size]`. The last statement is `this.size--;`."],"tests":[{"name":"Output","stdin":"","expect":"Turin is off the trip\nRome is off the trip\n[Lyon, Genoa, Pisa, Siena], 4 stops","hidden":false}],"require":[],"forbid":[],"more":[{"task":"This loop should remove every song whose title starts with B, but it removes while it goes forward through the list. `Playlist.java` has the methods from this step. What does the program print?\n","kind":"predict","seed":"public class Main {\n    public static void main(String[] args) {\n        Playlist list = new Playlist();\n        list.add(\"Anthem\");\n        list.add(\"Ballad\");\n        list.add(\"Blues\");\n        list.add(\"Carol\");\n        list.add(\"Bolero\");\n        for (int i = 0; i < list.size(); i++) {\n            if (list.get(i).startsWith(\"B\")) {\n                list.remove(i);\n            }\n        }\n        System.out.println(list);\n        System.out.println(list.size());\n    }\n}\n// ==== Playlist.java ====\npublic class Playlist {\n    private String[] songs;\n    private int size;\n\n    public Playlist() {\n        this.songs = new String[4];\n        this.size = 0;\n    }\n\n    public void add(String song) {\n        if (this.size == this.songs.length) {\n            grow();\n        }\n        this.songs[this.size] = song;\n        this.size++;\n    }\n\n    private void grow() {\n        String[] bigger = new String[this.songs.length * 2];\n        for (int i = 0; i < this.size; i++) {\n            bigger[i] = this.songs[i];\n        }\n        this.songs = bigger;\n    }\n\n    public int size() {\n        return this.size;\n    }\n\n    public String get(int index) {\n        if (index < 0 || index >= this.size) {\n            throw new IndexOutOfBoundsException(\"Index \" + index + \" out of bounds for size \" + this.size);\n        }\n        return this.songs[index];\n    }\n\n    public String remove(int index) {\n        String removed = get(index);\n        for (int i = index; i < this.size - 1; i++) {\n            this.songs[i] = this.songs[i + 1];\n        }\n        this.songs[this.size - 1] = null;\n        this.size--;\n        return removed;\n    }\n\n    @Override\n    public String toString() {\n        String text = \"[\";\n        for (int i = 0; i < this.size; i++) {\n            if (i > 0) {\n                text += \", \";\n            }\n            text += this.songs[i];\n        }\n        return text + \"]\";\n    }\n}\n","solution":"public class Main {\n    public static void main(String[] args) {\n        Playlist list = new Playlist();\n        list.add(\"Anthem\");\n        list.add(\"Ballad\");\n        list.add(\"Blues\");\n        list.add(\"Carol\");\n        list.add(\"Bolero\");\n        for (int i = 0; i < list.size(); i++) {\n            if (list.get(i).startsWith(\"B\")) {\n                list.remove(i);\n            }\n        }\n        System.out.println(list);\n        System.out.println(list.size());\n    }\n}\n// ==== Playlist.java ====\npublic class Playlist {\n    private String[] songs;\n    private int size;\n\n    public Playlist() {\n        this.songs = new String[4];\n        this.size = 0;\n    }\n\n    public void add(String song) {\n        if (this.size == this.songs.length) {\n            grow();\n        }\n        this.songs[this.size] = song;\n        this.size++;\n    }\n\n    private void grow() {\n        String[] bigger = new String[this.songs.length * 2];\n        for (int i = 0; i < this.size; i++) {\n            bigger[i] = this.songs[i];\n        }\n        this.songs = bigger;\n    }\n\n    public int size() {\n        return this.size;\n    }\n\n    public String get(int index) {\n        if (index < 0 || index >= this.size) {\n            throw new IndexOutOfBoundsException(\"Index \" + index + \" out of bounds for size \" + this.size);\n        }\n        return this.songs[index];\n    }\n\n    public String remove(int index) {\n        String removed = get(index);\n        for (int i = index; i < this.size - 1; i++) {\n            this.songs[i] = this.songs[i + 1];\n        }\n        this.songs[this.size - 1] = null;\n        this.size--;\n        return removed;\n    }\n\n    @Override\n    public String toString() {\n        String text = \"[\";\n        for (int i = 0; i < this.size; i++) {\n            if (i > 0) {\n                text += \", \";\n            }\n            text += this.songs[i];\n        }\n        return text + \"]\";\n    }\n}\n","hints":["When `i` is 1, Ballad is removed, and every song after it moves one place to the left: Blues is now at index 1. The loop then goes on with `i` = 2.","Write the list down after each removal and follow `i`. Blues is never looked at, and the loop stops when `i` reaches the new size."],"tests":[{"name":"Output","stdin":"","expect":"[Anthem, Blues, Carol]\n3","hidden":false}],"require":[],"forbid":[],"lines":["[Anthem, Blues, Carol]","3"]},{"task":"ArrayList's `add(int index, E element)` inserts an element at an index, and moves the ones from that index on one place to the right. Add the same to `Itinerary.java`: write `add(int index, String stop)`.\n\n- The index can be from 0 to the size; at the size, the stop goes at the end. Any other index is refused with an IndexOutOfBoundsException with the message `Index 5 out of bounds for size 3` (with the index and the size in it), as `remove` does.\n- When the array is full, it first grows, just as the other `add` does.\n- Then each stop from the index on moves one place to the right, the new stop goes in at the index, and the size goes up by one.\n\nWhich way must the loop go this time? The check runs:\n\n```java\nItinerary trip = new Itinerary();\ntrip.add(\"Lyon\");\ntrip.add(\"Genoa\");\ntrip.add(\"Siena\");\ntrip.add(1, \"Turin\");\ntrip.add(0, \"Geneva\");\ntrip.add(5, \"Rome\");\nSystem.out.println(trip + \", \" + trip.size() + \" stops\");\n```\n\nand expects:\n\n```\n[Geneva, Lyon, Turin, Genoa, Siena, Rome], 6 stops\n```\n","kind":"code","seed":"// ==== Itinerary.java ====\npublic class Itinerary {\n    private String[] stops;\n    private int size;\n\n    public Itinerary() {\n        this.stops = new String[4];\n        this.size = 0;\n    }\n\n    public void add(String stop) {\n        if (this.size == this.stops.length) {\n            grow();\n        }\n        this.stops[this.size] = stop;\n        this.size++;\n    }\n\n    // Write add(int index, String stop) here.\n\n    private void grow() {\n        String[] bigger = new String[this.stops.length * 2];\n        for (int i = 0; i < this.size; i++) {\n            bigger[i] = this.stops[i];\n        }\n        this.stops = bigger;\n    }\n\n    public int size() {\n        return this.size;\n    }\n\n    public String remove(int index) {\n        if (index < 0 || index >= this.size) {\n            throw new IndexOutOfBoundsException(\"Index \" + index + \" out of bounds for size \" + this.size);\n        }\n        String removed = this.stops[index];\n        for (int i = index; i < this.size - 1; i++) {\n            this.stops[i] = this.stops[i + 1];\n        }\n        this.stops[this.size - 1] = null;\n        this.size--;\n        return removed;\n    }\n\n    @Override\n    public String toString() {\n        String text = \"[\";\n        for (int i = 0; i < this.size; i++) {\n            if (i > 0) {\n                text += \", \";\n            }\n            text += this.stops[i];\n        }\n        return text + \"]\";\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        Itinerary trip = new Itinerary();\n        trip.add(\"Lyon\");\n        trip.add(\"Genoa\");\n        trip.add(\"Siena\");\n        trip.add(1, \"Turin\");\n        trip.add(0, \"Geneva\");\n        trip.add(5, \"Rome\");\n        System.out.println(trip + \", \" + trip.size() + \" stops\");\n    }\n}\n","solution":"// ==== Itinerary.java ====\npublic class Itinerary {\n    private String[] stops;\n    private int size;\n\n    public Itinerary() {\n        this.stops = new String[4];\n        this.size = 0;\n    }\n\n    public void add(String stop) {\n        if (this.size == this.stops.length) {\n            grow();\n        }\n        this.stops[this.size] = stop;\n        this.size++;\n    }\n\n    public void add(int index, String stop) {\n        if (index < 0 || index > this.size) {\n            throw new IndexOutOfBoundsException(\"Index \" + index + \" out of bounds for size \" + this.size);\n        }\n        if (this.size == this.stops.length) {\n            grow();\n        }\n        for (int i = this.size; i > index; i--) {\n            this.stops[i] = this.stops[i - 1];\n        }\n        this.stops[index] = stop;\n        this.size++;\n    }\n\n    private void grow() {\n        String[] bigger = new String[this.stops.length * 2];\n        for (int i = 0; i < this.size; i++) {\n            bigger[i] = this.stops[i];\n        }\n        this.stops = bigger;\n    }\n\n    public int size() {\n        return this.size;\n    }\n\n    public String remove(int index) {\n        if (index < 0 || index >= this.size) {\n            throw new IndexOutOfBoundsException(\"Index \" + index + \" out of bounds for size \" + this.size);\n        }\n        String removed = this.stops[index];\n        for (int i = index; i < this.size - 1; i++) {\n            this.stops[i] = this.stops[i + 1];\n        }\n        this.stops[this.size - 1] = null;\n        this.size--;\n        return removed;\n    }\n\n    @Override\n    public String toString() {\n        String text = \"[\";\n        for (int i = 0; i < this.size; i++) {\n            if (i > 0) {\n                text += \", \";\n            }\n            text += this.stops[i];\n        }\n        return text + \"]\";\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        Itinerary trip = new Itinerary();\n        trip.add(\"Lyon\");\n        trip.add(\"Genoa\");\n        trip.add(\"Siena\");\n        trip.add(1, \"Turin\");\n        trip.add(0, \"Geneva\");\n        trip.add(5, \"Rome\");\n        System.out.println(trip + \", \" + trip.size() + \" stops\");\n    }\n}\n","hints":["Check the index first: refuse it when `index < 0 || index > this.size` (the size itself is allowed here). Then grow if `this.size == this.stops.length`.","Move the stops from the back: `for (int i = this.size; i > index; i--) { this.stops[i] = this.stops[i - 1]; }`. From the front, the first move would overwrite the next stop before it moved. Then `this.stops[index] = stop;` and `this.size++;`."],"tests":[{"name":"Test 1","stdin":"","call":"Itinerary trip = new Itinerary();\ntrip.add(\"Lyon\");\ntrip.add(\"Genoa\");\ntrip.add(\"Siena\");\ntrip.add(1, \"Turin\");\ntrip.add(0, \"Geneva\");\ntrip.add(5, \"Rome\");\nSystem.out.println(trip + \", \" + trip.size() + \" stops\");\n","expect":"[Geneva, Lyon, Turin, Genoa, Siena, Rome], 6 stops","hidden":false},{"name":"Test 2","stdin":"","call":"Itinerary trip = new Itinerary();\ntrip.add(0, \"Bern\");\nSystem.out.println(trip + \" \" + trip.size());\ntry {\n    trip.add(2, \"Basel\");\n    System.out.println(\"added\");\n} catch (IndexOutOfBoundsException e) {\n    System.out.println(e.getMessage());\n}\ntry {\n    trip.add(-1, \"Basel\");\n    System.out.println(\"added\");\n} catch (IndexOutOfBoundsException e) {\n    System.out.println(e.getMessage());\n}\nSystem.out.println(trip + \" \" + trip.size());\n","expect":"[Bern] 1\nIndex 2 out of bounds for size 1\nIndex -1 out of bounds for size 1\n[Bern] 1","hidden":true},{"name":"Test 3","stdin":"","call":"Itinerary trip = new Itinerary();\nfor (int i = 1; i <= 9; i++) {\n    trip.add(0, \"Stop \" + i);\n}\ntrip.add(4, \"Lunch\");\nSystem.out.println(trip);\nSystem.out.println(trip.size() + \" \" + trip.remove(9) + \" \" + trip.remove(0));\n","expect":"[Stop 9, Stop 8, Stop 7, Stop 6, Lunch, Stop 5, Stop 4, Stop 3, Stop 2, Stop 1]\n10 Stop 1 Stop 9","hidden":true}],"require":[{"pattern":"\\bthrow\\s+new\\s+IndexOutOfBoundsException\\s*\\(","min":2,"message":"Refuse a bad index in the new `add` with `throw new IndexOutOfBoundsException(...)`, as `remove` does."}],"forbid":[]}]}