{"id":"recap-together","slug":"putting-it-together","title":"Putting it together","text":"The last step puts the tools of the earlier steps into one program, the way module 29 did: a class for the data, a class for the logic, and a part that reads the input and prints the answers. This time the data class has an `equals`, and the logic class lets the list methods use it.\n\n### The tools\n\n| Tool | What it does | Taught in |\n| --- | --- | --- |\n| an `ArrayList` of objects | many objects, and loops that ask them questions | module 19, Objects in a list |\n| `new Scanner(Paths.get(fileName))` in a `try` | reads a file, line by line | module 20, Reading files |\n| `null` for \"not found\" | an answer the caller checks | module 22, Primitive and reference variables |\n| `contains`, `indexOf` and `remove(value)` on a list of your own objects | search the list with the class's `equals` | module 23, Objects working together |\n| a class that keeps a list as an instance variable | the logic, apart from the input and output | module 24, Lists inside objects |\n| a loop that reads commands | a text user interface | module 25, Separating the UI from program logic |\n\n### A worked example\n\nThe climbing gym of step 4 keeps its routes in a file, one per line: the wall, the color and the grade. Someone typed the red route on the north wall twice, the second time with another grade. A route is known by its wall and color, so the second line is the same route, and it must not end up in the list twice. At the front desk, climbers then log their ascents with the command `climb` and a line such as `north wall,red`.\n\n`ClimbingRoute` is step 4's class, with its `equals`. Its getters are left out here, since this program doesn't use them.\n\n```file routes.csv\nnorth wall,red,5\nnorth wall,blue,3\noverhang,green,4\nnorth wall,red,6\nslab,yellow,2\n```\n\n```java\nimport java.nio.file.Paths;\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Gym gym = readGym(\"routes.csv\");\n        Scanner scanner = new Scanner(System.in);\n        while (true) {\n            System.out.print(\"> \");\n            String command = scanner.nextLine();\n            if (command.equals(\"quit\")) {\n                System.out.println(\"Gym closed\");\n                break;\n            }\n            if (command.equals(\"climb\")) {\n                String[] parts = scanner.nextLine().split(\",\");\n                ClimbingRoute route = gym.find(parts[0], parts[1]);\n                if (route == null) {\n                    System.out.println(\"No \" + parts[1] + \" route on the \" + parts[0]);\n                } else {\n                    route.climb();\n                    System.out.println(route);\n                }\n            } else if (command.equals(\"routes\")) {\n                for (ClimbingRoute route : gym.routes()) {\n                    System.out.println(route);\n                }\n            } else {\n                System.out.println(\"Unknown command: \" + command);\n            }\n        }\n    }\n\n    public static Gym readGym(String fileName) {\n        Gym gym = new Gym();\n        try (Scanner scanner = new Scanner(Paths.get(fileName))) {\n            while (scanner.hasNextLine()) {\n                String[] parts = scanner.nextLine().split(\",\");\n                ClimbingRoute route = new ClimbingRoute(parts[0], parts[1], Integer.valueOf(parts[2]));\n                if (!gym.add(route)) {\n                    System.out.println(\"Listed twice: \" + route);\n                }\n            }\n        } catch (Exception e) {\n            System.out.println(\"Error: \" + e.getMessage());\n        }\n        return gym;\n    }\n}\n// ==== Gym.java ====\nimport java.util.ArrayList;\n\npublic class Gym {\n    private ArrayList<ClimbingRoute> routes;\n\n    public Gym() {\n        this.routes = new ArrayList<>();\n    }\n\n    public boolean add(ClimbingRoute route) {\n        if (this.routes.contains(route)) {\n            return false;\n        }\n        this.routes.add(route);\n        return true;\n    }\n\n    public ClimbingRoute find(String wall, String color) {\n        int index = this.routes.indexOf(new ClimbingRoute(wall, color, 0));\n        if (index < 0) {\n            return null;\n        }\n        return this.routes.get(index);\n    }\n\n    public ArrayList<ClimbingRoute> routes() {\n        return this.routes;\n    }\n}\n// ==== ClimbingRoute.java ====\npublic class ClimbingRoute {\n    private String wall;\n    private String color;\n    private int grade;\n    private int ascents;\n\n    public ClimbingRoute(String wall, String color, int grade) {\n        this.wall = wall;\n        this.color = color;\n        this.grade = grade;\n        this.ascents = 0;\n    }\n\n    public void climb() {\n        this.ascents++;\n    }\n\n    public boolean equals(Object compared) {\n        if (this == compared) {\n            return true;\n        }\n        if (!(compared instanceof ClimbingRoute)) {\n            return false;\n        }\n        ClimbingRoute other = (ClimbingRoute) compared;\n        return this.wall.equals(other.wall) && this.color.equals(other.color);\n    }\n\n    public String toString() {\n        return this.color + \" route on the \" + this.wall + \", grade \" + this.grade + \", ascents: \" + this.ascents;\n    }\n}\n```\n\n```input\nclimb\nnorth wall,red\nclimb\noverhang,green\nclimb\nnorth wall,red\nclimb\nslab,blue\nroutes\nquit\n```\n\n```output\nListed twice: red route on the north wall, grade 6, ascents: 0\n> red route on the north wall, grade 5, ascents: 1\n> green route on the overhang, grade 4, ascents: 1\n> red route on the north wall, grade 5, ascents: 2\n> No blue route on the slab\n> red route on the north wall, grade 5, ascents: 2\nblue route on the north wall, grade 3, ascents: 0\ngreen route on the overhang, grade 4, ascents: 1\nyellow route on the slab, grade 2, ascents: 0\n> Gym closed\n```\n\n- `ClimbingRoute` is the data, `Gym` is the logic, and `main` reads the file and the commands. Gym holds the list and answers with return values, `false` and `null` included, but prints nothing: `main` turns the answers into messages.\n- `add` asks `contains` first. `contains` compares with `equals`, so the fourth line of the file is found in the list, although it's a new object with another grade, and it's not added.\n- `find` needs a route to search with, so it makes one from the wall and the color. The grade doesn't matter to `equals`, so 0 will do. `indexOf` gives the index of the equal route in the list, and `get` gives that route itself, not a copy, so `route.climb()` in `main` changes the route in the list. The `routes` command shows the count.\n- `contains` and `indexOf` go through the list from the start, and compare each route with `equals` until one matches. A gym with a thousand routes can take a thousand comparisons for every search. Module 31's hash map finds a value by its key without going through the others.\n","task":"At the end of a woodworking course, each student's project is typed in, one per line, until an empty line: the student, the project and the hours it took, like `Ada,bird box,6`. `Project.java` is ready. In `Main.java`, read the projects into the `ArrayList<Project>`. The average isn't known until the last project has been read, so the list keeps them until then. After reading, print:\n\n- `Projects: ` and the number of projects,\n- `Average: `, the average hours as a `double` (the hours added up, divided by the number of projects), and ` hours`,\n- `Longer than average:`, and on the lines after it every project that took more hours than the average, in the order they were typed.\n\nWith no projects, print only `No projects`. For the input shown, the program prints:\n\n```\nProjects: 4\nAverage: 7.5 hours\nLonger than average:\nBen: stool, 11 hours\nDev: bookshelf, 9 hours\n```\n","kind":"code","seed":"// ==== Main.java ====\nimport java.util.ArrayList;\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        ArrayList<Project> projects = new ArrayList<>();\n\n    }\n}\n\n// ==== Project.java ====\npublic class Project {\n    private String student;\n    private String name;\n    private int hours;\n\n    public Project(String student, String name, int hours) {\n        this.student = student;\n        this.name = name;\n        this.hours = hours;\n    }\n\n    public String getStudent() {\n        return this.student;\n    }\n\n    public int getHours() {\n        return this.hours;\n    }\n\n    public String toString() {\n        return this.student + \": \" + this.name + \", \" + this.hours + \" hours\";\n    }\n}\n","solution":"// ==== Main.java ====\nimport java.util.ArrayList;\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        ArrayList<Project> projects = new ArrayList<>();\n        while (true) {\n            String line = scanner.nextLine();\n            if (line.isEmpty()) {\n                break;\n            }\n            String[] parts = line.split(\",\");\n            projects.add(new Project(parts[0], parts[1], Integer.valueOf(parts[2])));\n        }\n        if (projects.isEmpty()) {\n            System.out.println(\"No projects\");\n        } else {\n            int total = 0;\n            for (Project project : projects) {\n                total += project.getHours();\n            }\n            double average = (double) total / projects.size();\n            System.out.println(\"Projects: \" + projects.size());\n            System.out.println(\"Average: \" + average + \" hours\");\n            System.out.println(\"Longer than average:\");\n            for (Project project : projects) {\n                if (project.getHours() > average) {\n                    System.out.println(project);\n                }\n            }\n        }\n    }\n}\n\n// ==== Project.java ====\npublic class Project {\n    private String student;\n    private String name;\n    private int hours;\n\n    public Project(String student, String name, int hours) {\n        this.student = student;\n        this.name = name;\n        this.hours = hours;\n    }\n\n    public String getStudent() {\n        return this.student;\n    }\n\n    public int getHours() {\n        return this.hours;\n    }\n\n    public String toString() {\n        return this.student + \": \" + this.name + \", \" + this.hours + \" hours\";\n    }\n}\n","hints":["Read in a `while (true)` loop that stops at the empty line. Split each line at the commas, and add `new Project(parts[0], parts[1], Integer.valueOf(parts[2]))` to the list.","After the loop, check `projects.isEmpty()` first. Then one for-each loop adds up the hours, `(double) total / projects.size()` is the average, and a second loop prints each project whose `getHours()` is greater than the average."],"tests":[{"name":"Test 1","stdin":"Ada,bird box,6\nBen,stool,11\nCleo,cutting board,4\nDev,bookshelf,9\n\n","expect":"Projects: 4\nAverage: 7.5 hours\nLonger than average:\nBen: stool, 11 hours\nDev: bookshelf, 9 hours","hidden":false},{"name":"Test 2","stdin":"\n","expect":"No projects","hidden":true},{"name":"Test 3","stdin":"Eli,spoon,3\nFay,spoon,3\n\n","expect":"Projects: 2\nAverage: 3.0 hours\nLonger than average:","hidden":true},{"name":"Test 4","stdin":"Gus,toolbox,8\nHana,step stool,7\nIvo,picture frame,4\n\n","expect":"Projects: 3\nAverage: 6.333333333333333 hours\nLonger than average:\nGus: toolbox, 8 hours\nHana: step stool, 7 hours","hidden":true}],"require":[],"forbid":[],"more":[{"task":"A climbing gym rents out climbing shoes, and has 2 pairs of one size on the shelf. This program reads its commands from a String, as module 20 showed a Scanner can. What does it print?\n","kind":"predict","seed":"import java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(\"rent\\nrent\\nreturn\\nrent\\nlist\\nrent\\nquit\\nrent\\n\");\n        int pairs = 2;\n        while (true) {\n            String command = scanner.nextLine();\n            if (command.equals(\"quit\")) {\n                break;\n            }\n            if (command.equals(\"rent\")) {\n                if (pairs == 0) {\n                    System.out.println(\"No shoes left\");\n                } else {\n                    pairs--;\n                    System.out.println(\"Rented, \" + pairs + \" left\");\n                }\n            } else if (command.equals(\"return\")) {\n                pairs++;\n            } else {\n                System.out.println(\"Unknown: \" + command);\n            }\n        }\n        System.out.println(\"Pairs on the shelf: \" + pairs);\n    }\n}\n","solution":"import java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(\"rent\\nrent\\nreturn\\nrent\\nlist\\nrent\\nquit\\nrent\\n\");\n        int pairs = 2;\n        while (true) {\n            String command = scanner.nextLine();\n            if (command.equals(\"quit\")) {\n                break;\n            }\n            if (command.equals(\"rent\")) {\n                if (pairs == 0) {\n                    System.out.println(\"No shoes left\");\n                } else {\n                    pairs--;\n                    System.out.println(\"Rented, \" + pairs + \" left\");\n                }\n            } else if (command.equals(\"return\")) {\n                pairs++;\n            } else {\n                System.out.println(\"Unknown: \" + command);\n            }\n        }\n        System.out.println(\"Pairs on the shelf: \" + pairs);\n    }\n}\n","hints":["Go through the commands one at a time. `return` adds a pair back to the shelf, but it prints nothing. `list` isn't a command the program knows.","When `pairs` is 0, `rent` only prints a message. `quit` ends the loop, so the `rent` after it is never read, and the last line prints the pairs on the shelf."],"tests":[{"name":"Output","stdin":"","expect":"Rented, 1 left\nRented, 0 left\nRented, 0 left\nUnknown: list\nNo shoes left\nPairs on the shelf: 0","hidden":false}],"require":[],"forbid":[],"lines":["Rented, 1 left","Rented, 0 left","Rented, 0 left","Unknown: list","No shoes left","Pairs on the shelf: 0"]},{"task":"The school play of step 4 keeps its cast in a `Cast` object. `Role.java` is step 4's class with its `equals`: two roles are equal when they're the same character in the same play, whoever plays it. Role has no getters for the play or the character, so a search for a role goes through `equals`. In `Cast.java`, replace the placeholders:\n\n- `add(Role role)` adds the role to the list and returns `true`. When an equal role is already in the list, it changes nothing and returns `false`.\n- `recast(Role role)` gives a role that's already in the list to another student: it puts the role it gets in the place of the equal role, at the same index, and returns `true`. When no equal role is in the list, it changes nothing and returns `false`.\n- `studentFor(String play, String character)` returns the student who plays that character in that play, or `null` when nobody does.\n\nUse the list's `contains` and `indexOf`, which compare with Role's `equals`. Cast doesn't print anything. `Main.java` is a test drive, and it prints:\n\n```\ntrue\ntrue\nfalse\ntrue\nfalse\nTheo null\n[Ariel in The Tempest: Theo, Miranda in The Tempest: Liv]\n```\n","kind":"code","seed":"// ==== Cast.java ====\nimport java.util.ArrayList;\n\npublic class Cast {\n    private ArrayList<Role> roles;\n\n    public Cast() {\n        this.roles = new ArrayList<>();\n    }\n\n    public boolean add(Role role) {\n        // Replace the placeholders with the real code.\n        return false;\n    }\n\n    public boolean recast(Role role) {\n        return false;\n    }\n\n    public String studentFor(String play, String character) {\n        return null;\n    }\n\n    public ArrayList<Role> roles() {\n        return this.roles;\n    }\n}\n\n// ==== Role.java ====\npublic class Role {\n    private String play;\n    private String character;\n    private String student;\n\n    public Role(String play, String character, String student) {\n        this.play = play;\n        this.character = character;\n        this.student = student;\n    }\n\n    public String getStudent() {\n        return this.student;\n    }\n\n    public boolean equals(Object compared) {\n        if (this == compared) {\n            return true;\n        }\n        if (!(compared instanceof Role)) {\n            return false;\n        }\n        Role other = (Role) compared;\n        return this.play.equals(other.play) && this.character.equals(other.character);\n    }\n\n    public String toString() {\n        return this.character + \" in \" + this.play + \": \" + this.student;\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        Cast cast = new Cast();\n        System.out.println(cast.add(new Role(\"The Tempest\", \"Ariel\", \"Noor\")));\n        System.out.println(cast.add(new Role(\"The Tempest\", \"Miranda\", \"Liv\")));\n        System.out.println(cast.add(new Role(\"The Tempest\", \"Ariel\", \"Theo\")));\n        System.out.println(cast.recast(new Role(\"The Tempest\", \"Ariel\", \"Theo\")));\n        System.out.println(cast.recast(new Role(\"Macbeth\", \"Banquo\", \"Eli\")));\n        System.out.println(cast.studentFor(\"The Tempest\", \"Ariel\") + \" \" + cast.studentFor(\"The Tempest\", \"Prospero\"));\n        System.out.println(cast.roles());\n    }\n}\n","solution":"// ==== Cast.java ====\nimport java.util.ArrayList;\n\npublic class Cast {\n    private ArrayList<Role> roles;\n\n    public Cast() {\n        this.roles = new ArrayList<>();\n    }\n\n    public boolean add(Role role) {\n        // Replace the placeholders with the real code.\n        if (this.roles.contains(role)) {\n            return false;\n        }\n        this.roles.add(role);\n        return true;\n    }\n\n    public boolean recast(Role role) {\n        int index = this.roles.indexOf(role);\n        if (index < 0) {\n            return false;\n        }\n        this.roles.set(index, role);\n        return true;\n    }\n\n    public String studentFor(String play, String character) {\n        int index = this.roles.indexOf(new Role(play, character, \"\"));\n        if (index < 0) {\n            return null;\n        }\n        return this.roles.get(index).getStudent();\n    }\n\n    public ArrayList<Role> roles() {\n        return this.roles;\n    }\n}\n\n// ==== Role.java ====\npublic class Role {\n    private String play;\n    private String character;\n    private String student;\n\n    public Role(String play, String character, String student) {\n        this.play = play;\n        this.character = character;\n        this.student = student;\n    }\n\n    public String getStudent() {\n        return this.student;\n    }\n\n    public boolean equals(Object compared) {\n        if (this == compared) {\n            return true;\n        }\n        if (!(compared instanceof Role)) {\n            return false;\n        }\n        Role other = (Role) compared;\n        return this.play.equals(other.play) && this.character.equals(other.character);\n    }\n\n    public String toString() {\n        return this.character + \" in \" + this.play + \": \" + this.student;\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        Cast cast = new Cast();\n        System.out.println(cast.add(new Role(\"The Tempest\", \"Ariel\", \"Noor\")));\n        System.out.println(cast.add(new Role(\"The Tempest\", \"Miranda\", \"Liv\")));\n        System.out.println(cast.add(new Role(\"The Tempest\", \"Ariel\", \"Theo\")));\n        System.out.println(cast.recast(new Role(\"The Tempest\", \"Ariel\", \"Theo\")));\n        System.out.println(cast.recast(new Role(\"Macbeth\", \"Banquo\", \"Eli\")));\n        System.out.println(cast.studentFor(\"The Tempest\", \"Ariel\") + \" \" + cast.studentFor(\"The Tempest\", \"Prospero\"));\n        System.out.println(cast.roles());\n    }\n}\n","hints":["`add` is the Gym's `add` from the lesson: `contains(role)` first, and only then `add`. In `recast`, `indexOf(role)` finds the equal role, since `equals` ignores the student, and `this.roles.set(index, role)` puts the new one in its place (module 14).","`studentFor` needs a role to search with, like the Gym's `find`: `new Role(play, character, \"\")`. The student doesn't matter to `equals`, so any text will do. When `indexOf` gives -1, return `null`, and otherwise `get(index).getStudent()`."],"tests":[{"name":"The test drive","stdin":"","expect":"true\ntrue\nfalse\ntrue\nfalse\nTheo null\n[Ariel in The Tempest: Theo, Miranda in The Tempest: Liv]","hidden":false},{"name":"Test 2","stdin":"","call":"Cast cast = new Cast();\nSystem.out.println(cast.recast(new Role(\"Twelfth Night\", \"Viola\", \"Ida\")) + \" \" + cast.studentFor(\"Twelfth Night\", \"Viola\"));\nSystem.out.println(cast.add(new Role(\"Twelfth Night\", \"Sebastian\", \"Oskar\")) + \" \" + cast.add(new Role(\"The Tempest\", \"Sebastian\", \"Pia\")));\nSystem.out.println(cast.studentFor(\"The Tempest\", \"Sebastian\") + \" \" + cast.studentFor(\"Twelfth Night\", \"Sebastian\"));\nSystem.out.println(cast.roles().size());\n","expect":"false null\ntrue true\nPia Oskar\n2","hidden":true},{"name":"Test 3","stdin":"","call":"Cast cast = new Cast();\ncast.add(new Role(\"Macbeth\", \"Banquo\", \"Eli\"));\ncast.add(new Role(\"Macbeth\", \"Macduff\", \"Fay\"));\ncast.add(new Role(\"Macbeth\", \"Hecate\", \"Gus\"));\nSystem.out.println(cast.recast(new Role(\"Macbeth\", \"Macduff\", \"Hana\")));\nSystem.out.println(cast.add(new Role(\"Macbeth\", \"Macduff\", \"Ivo\")));\nSystem.out.println(cast.roles());\n","expect":"true\nfalse\n[Banquo in Macbeth: Eli, Macduff in Macbeth: Hana, Hecate in Macbeth: Gus]","hidden":true}],"require":[{"pattern":"\\.indexOf\\s*\\(","message":"Find the equal role with the list's `indexOf`, which compares with Role's `equals`."}],"forbid":[]}]}