{"id":"larger-programs-file-questions","slug":"questions-about-a-file","title":"Questions about a file of records","text":"Many programs start from a file. The data is read once, each line into an object, and then the program answers questions about it. Step 1's plan fits here too: a class for one record, a class for the logic that holds the list and answers the questions, and the input part, which now reads a file as well as the keyboard.\n\nA holiday company rents out cabins, and its file has one cabin per line: the name, the number of beds and the price of a night, separated by commas. The first line is a header (module 20), and someone left a line unfinished. The program reads the size of a group from the keyboard and answers three questions: which cabins have enough beds, how many beds there are in all, and which cabin is the cheapest one that fits the group.\n\n```file cabins.csv\nname,beds,price\nBirch,4,90\nLakeside,6,140\nOld Mill,2\nPine Hill,6,120\nFox Den,8,180\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        CabinList cabins = readCabins(\"cabins.csv\");\n        Scanner scanner = new Scanner(System.in);\n        int people = Integer.valueOf(scanner.nextLine());\n        System.out.println(\"Cabins with \" + people + \" beds or more:\");\n        for (Cabin cabin : cabins.withBeds(people)) {\n            System.out.println(cabin);\n        }\n        System.out.println(\"Beds in all: \" + cabins.totalBeds());\n        Cabin cheapest = cabins.cheapestFor(people);\n        if (cheapest == null) {\n            System.out.println(\"No cabin fits \" + people);\n        } else {\n            System.out.println(\"Cheapest for \" + people + \": \" + cheapest);\n        }\n    }\n\n    public static CabinList readCabins(String fileName) {\n        CabinList cabins = new CabinList();\n        try (Scanner scanner = new Scanner(Paths.get(fileName))) {\n            scanner.nextLine();\n            while (scanner.hasNextLine()) {\n                String[] parts = scanner.nextLine().split(\",\");\n                if (parts.length < 3) {\n                    continue;\n                }\n                cabins.add(new Cabin(parts[0], Integer.valueOf(parts[1]), Integer.valueOf(parts[2])));\n            }\n        } catch (Exception e) {\n            System.out.println(\"Error: \" + e.getMessage());\n        }\n        return cabins;\n    }\n}\n// ==== Cabin.java ====\npublic class Cabin {\n    private String name;\n    private int beds;\n    private int price;\n\n    public Cabin(String name, int beds, int price) {\n        this.name = name;\n        this.beds = beds;\n        this.price = price;\n    }\n\n    public int getBeds() {\n        return this.beds;\n    }\n\n    public int getPrice() {\n        return this.price;\n    }\n\n    public String toString() {\n        return this.name + \" (\" + this.beds + \" beds, \" + this.price + \" euros)\";\n    }\n}\n// ==== CabinList.java ====\nimport java.util.ArrayList;\n\npublic class CabinList {\n    private ArrayList<Cabin> cabins;\n\n    public CabinList() {\n        this.cabins = new ArrayList<>();\n    }\n\n    public void add(Cabin cabin) {\n        this.cabins.add(cabin);\n    }\n\n    public ArrayList<Cabin> withBeds(int people) {\n        ArrayList<Cabin> fitting = new ArrayList<>();\n        for (Cabin cabin : this.cabins) {\n            if (cabin.getBeds() >= people) {\n                fitting.add(cabin);\n            }\n        }\n        return fitting;\n    }\n\n    public int totalBeds() {\n        int total = 0;\n        for (Cabin cabin : this.cabins) {\n            total += cabin.getBeds();\n        }\n        return total;\n    }\n\n    public Cabin cheapestFor(int people) {\n        Cabin cheapest = null;\n        for (Cabin cabin : this.cabins) {\n            if (cabin.getBeds() < people) {\n                continue;\n            }\n            if (cheapest == null || cabin.getPrice() < cheapest.getPrice()) {\n                cheapest = cabin;\n            }\n        }\n        return cheapest;\n    }\n}\n```\n\n```input\n5\n```\n\n```output\nCabins with 5 beds or more:\nLakeside (6 beds, 140 euros)\nPine Hill (6 beds, 120 euros)\nFox Den (8 beds, 180 euros)\nBeds in all: 24\nCheapest for 5: Pine Hill (6 beds, 120 euros)\n```\n\n### Reading: one line, one object\n\n`readCabins` is module 20's reading loop, with one check added. `Old Mill,2` splits into only two parts, and `parts[2]` would throw an exception. Inside the `try`, that would end the reading at that line, and the cabins after it would be lost. `parts.length < 3` catches the line first, and `continue` skips it, so Pine Hill and Fox Den are still read. That's why the beds add up to 24: Old Mill isn't in the list.\n\nOne record per line keeps the reading simple: every line is split in the same way, and every line becomes one object. The method returns the whole CabinList, so `main` starts with a single line, and the questions never see the file at all.\n\n### Three kinds of questions\n\nMost questions about records are one of three kinds, and each has its own loop shape:\n\n- **A filter**: the records that match a condition. `withBeds` puts them in a new list and returns it, so the caller decides what to do with them: print them, count them or ask more questions about them.\n- **A total**: add up one value of every record, as in `totalBeds`.\n- **The best match**: of the records that match, the one that's best in some way. `cheapestFor` combines the two others. `continue` skips the cabins that are too small, and the rest compete as in step 1's `longest`: the best so far starts as `null`, and a cabin takes its place when it's cheaper. When no cabin fits, the method returns `null`, and `main` prints `No cabin fits 9` for a group of 9.\n","task":"A city's charging stations for electric cars are in the file `stations.csv`. After a header line, each line has a station's name, how many of its chargers are free, and how many chargers it has in all, like `Harbor,3,6`. Some lines are broken: an empty line, or a line with fewer than three values. `Station.java` is ready, and so is `main`, which asks the questions. In `Main.java`, complete `readStations`: turn each line into a `Station` and add it to the list, and skip every line that splits into fewer than three parts. For the file shown below, the program prints:\n\n```\nStations: 4\nNothing free at:\nMarket Square\nPark Gate\nFree chargers in all: 10\nMost chargers: Market Square\n```\n","kind":"code","seed":"// ==== Main.java ====\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        ArrayList<Station> stations = readStations(\"stations.csv\");\n        System.out.println(\"Stations: \" + stations.size());\n        System.out.println(\"Nothing free at:\");\n        int free = 0;\n        Station biggest = null;\n        for (Station station : stations) {\n            if (station.getFree() == 0) {\n                System.out.println(station.getName());\n            }\n            free += station.getFree();\n            if (biggest == null || station.getChargers() > biggest.getChargers()) {\n                biggest = station;\n            }\n        }\n        System.out.println(\"Free chargers in all: \" + free);\n        if (biggest != null) {\n            System.out.println(\"Most chargers: \" + biggest.getName());\n        }\n    }\n\n    public static ArrayList<Station> readStations(String fileName) {\n        ArrayList<Station> stations = new ArrayList<>();\n        try (Scanner scanner = new Scanner(Paths.get(fileName))) {\n            scanner.nextLine();\n            while (scanner.hasNextLine()) {\n                String line = scanner.nextLine();\n                // Turn the line into a Station, and skip lines of fewer than three parts.\n            }\n        } catch (Exception e) {\n            System.out.println(\"Error: \" + e.getMessage());\n        }\n        return stations;\n    }\n}\n\n// ==== Station.java ====\npublic class Station {\n    private String name;\n    private int free;\n    private int chargers;\n\n    public Station(String name, int free, int chargers) {\n        this.name = name;\n        this.free = free;\n        this.chargers = chargers;\n    }\n\n    public String getName() {\n        return this.name;\n    }\n\n    public int getFree() {\n        return this.free;\n    }\n\n    public int getChargers() {\n        return this.chargers;\n    }\n}\n","solution":"// ==== Main.java ====\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        ArrayList<Station> stations = readStations(\"stations.csv\");\n        System.out.println(\"Stations: \" + stations.size());\n        System.out.println(\"Nothing free at:\");\n        int free = 0;\n        Station biggest = null;\n        for (Station station : stations) {\n            if (station.getFree() == 0) {\n                System.out.println(station.getName());\n            }\n            free += station.getFree();\n            if (biggest == null || station.getChargers() > biggest.getChargers()) {\n                biggest = station;\n            }\n        }\n        System.out.println(\"Free chargers in all: \" + free);\n        if (biggest != null) {\n            System.out.println(\"Most chargers: \" + biggest.getName());\n        }\n    }\n\n    public static ArrayList<Station> readStations(String fileName) {\n        ArrayList<Station> stations = new ArrayList<>();\n        try (Scanner scanner = new Scanner(Paths.get(fileName))) {\n            scanner.nextLine();\n            while (scanner.hasNextLine()) {\n                String line = scanner.nextLine();\n                // Turn the line into a Station, and skip lines of fewer than three parts.\n                String[] parts = line.split(\",\");\n                if (parts.length < 3) {\n                    continue;\n                }\n                stations.add(new Station(parts[0], Integer.valueOf(parts[1]), Integer.valueOf(parts[2])));\n            }\n        } catch (Exception e) {\n            System.out.println(\"Error: \" + e.getMessage());\n        }\n        return stations;\n    }\n}\n\n// ==== Station.java ====\npublic class Station {\n    private String name;\n    private int free;\n    private int chargers;\n\n    public Station(String name, int free, int chargers) {\n        this.name = name;\n        this.free = free;\n        this.chargers = chargers;\n    }\n\n    public String getName() {\n        return this.name;\n    }\n\n    public int getFree() {\n        return this.free;\n    }\n\n    public int getChargers() {\n        return this.chargers;\n    }\n}\n","hints":["Split the line at the commas with `line.split(\",\")`. An empty line splits into one part, the empty text, so the same check skips it too.","`if (parts.length < 3) { continue; }` skips a broken line. Then `stations.add(new Station(parts[0], Integer.valueOf(parts[1]), Integer.valueOf(parts[2])));`."],"tests":[{"name":"Test 1","stdin":"","files":{"stations.csv":"station,free,chargers\nHarbor,3,6\nMarket Square,0,8\n\nLibrary,7,7\nOld Town,0\nPark Gate,0,4\n"},"expect":"Stations: 4\nNothing free at:\nMarket Square\nPark Gate\nFree chargers in all: 10\nMost chargers: Market Square","hidden":false},{"name":"Test 2","stdin":"","files":{"stations.csv":"station,free,chargers\nNorth End,5,5\nRiverside,2,11\nUniversity,12,1\n"},"expect":"Stations: 3\nNothing free at:\nFree chargers in all: 19\nMost chargers: Riverside","hidden":true},{"name":"Test 3","stdin":"","files":{"stations.csv":"station,free,chargers\nOld Town\n\nMuseum,0,20\n"},"expect":"Stations: 1\nNothing free at:\nMuseum\nFree chargers in all: 0\nMost chargers: Museum","hidden":true},{"name":"Test 4","stdin":"","files":{"stations.csv":"station,free,chargers\n"},"expect":"Stations: 0\nNothing free at:\nFree chargers in all: 0","hidden":true}],"require":[],"forbid":[],"more":[{"task":"A school sports day saves its results in `results.csv`, one per line: the athlete's name, the event and the result in centimeters, like `Ada,long jump,412`. `Result.java` is ready, and so is `readResults`, which reads the file into a list. In `main`, read an event's name from the keyboard, and then print:\n\n- every result in that event, in the file's order,\n- `Results: ` and how many results the event has,\n- `Best: ` and the best result, the one with the most centimeters. If two are equal, the first one.\n\nIf the event has no results, print only `No results for ` and the event's name. For the input `long jump` and the file shown below, the program prints:\n\n```\nAda (412 cm)\nCleo (388 cm)\nDev (431 cm)\nResults: 3\nBest: Dev (431 cm)\n```\n","kind":"code","seed":"// ==== Main.java ====\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        ArrayList<Result> results = readResults(\"results.csv\");\n        Scanner scanner = new Scanner(System.in);\n        String event = scanner.nextLine();\n        // Answer the questions about the event here.\n    }\n\n    public static ArrayList<Result> readResults(String fileName) {\n        ArrayList<Result> results = new ArrayList<>();\n        try (Scanner scanner = new Scanner(Paths.get(fileName))) {\n            while (scanner.hasNextLine()) {\n                String[] parts = scanner.nextLine().split(\",\");\n                if (parts.length < 3) {\n                    continue;\n                }\n                results.add(new Result(parts[0], parts[1], Integer.valueOf(parts[2])));\n            }\n        } catch (Exception e) {\n            System.out.println(\"Error: \" + e.getMessage());\n        }\n        return results;\n    }\n}\n\n// ==== Result.java ====\npublic class Result {\n    private String name;\n    private String event;\n    private int centimeters;\n\n    public Result(String name, String event, int centimeters) {\n        this.name = name;\n        this.event = event;\n        this.centimeters = centimeters;\n    }\n\n    public String getName() {\n        return this.name;\n    }\n\n    public String getEvent() {\n        return this.event;\n    }\n\n    public int getCentimeters() {\n        return this.centimeters;\n    }\n\n    public String toString() {\n        return this.name + \" (\" + this.centimeters + \" cm)\";\n    }\n}\n","solution":"// ==== Main.java ====\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        ArrayList<Result> results = readResults(\"results.csv\");\n        Scanner scanner = new Scanner(System.in);\n        String event = scanner.nextLine();\n        // Answer the questions about the event here.\n        int count = 0;\n        Result best = null;\n        for (Result result : results) {\n            if (!result.getEvent().equals(event)) {\n                continue;\n            }\n            System.out.println(result);\n            count++;\n            if (best == null || result.getCentimeters() > best.getCentimeters()) {\n                best = result;\n            }\n        }\n        if (count == 0) {\n            System.out.println(\"No results for \" + event);\n        } else {\n            System.out.println(\"Results: \" + count);\n            System.out.println(\"Best: \" + best);\n        }\n    }\n\n    public static ArrayList<Result> readResults(String fileName) {\n        ArrayList<Result> results = new ArrayList<>();\n        try (Scanner scanner = new Scanner(Paths.get(fileName))) {\n            while (scanner.hasNextLine()) {\n                String[] parts = scanner.nextLine().split(\",\");\n                if (parts.length < 3) {\n                    continue;\n                }\n                results.add(new Result(parts[0], parts[1], Integer.valueOf(parts[2])));\n            }\n        } catch (Exception e) {\n            System.out.println(\"Error: \" + e.getMessage());\n        }\n        return results;\n    }\n}\n\n// ==== Result.java ====\npublic class Result {\n    private String name;\n    private String event;\n    private int centimeters;\n\n    public Result(String name, String event, int centimeters) {\n        this.name = name;\n        this.event = event;\n        this.centimeters = centimeters;\n    }\n\n    public String getName() {\n        return this.name;\n    }\n\n    public String getEvent() {\n        return this.event;\n    }\n\n    public int getCentimeters() {\n        return this.centimeters;\n    }\n\n    public String toString() {\n        return this.name + \" (\" + this.centimeters + \" cm)\";\n    }\n}\n","hints":["One for-each loop answers all three questions. Skip a result with `continue` when `!result.getEvent().equals(event)`, and print, count and compare the rest.","Keep `Result best = null;` before the loop, and replace it when it's `null` or when the result has more centimeters (`>`, so that the first of two equal ones stays). After the loop, a count of 0 means no results."],"tests":[{"name":"Test 1","stdin":"long jump\n","files":{"results.csv":"Ada,long jump,412\nBen,shot put,905\nCleo,long jump,388\nDev,long jump,431\nEli,shot put,870\n"},"expect":"Ada (412 cm)\nCleo (388 cm)\nDev (431 cm)\nResults: 3\nBest: Dev (431 cm)","hidden":false},{"name":"Test 2","stdin":"high jump\n","files":{"results.csv":"Ada,long jump,412\nBen,shot put,905\n"},"expect":"No results for high jump","hidden":true},{"name":"Test 3","stdin":"shot put\n","files":{"results.csv":"Fay,shot put,910\nGus,long jump,350\nHana,shot put,1020\nIvo,shot put,1020\nJan,Shot put,1200\n"},"expect":"Fay (910 cm)\nHana (1020 cm)\nIvo (1020 cm)\nResults: 3\nBest: Hana (1020 cm)","hidden":true}],"require":[],"forbid":[]},{"task":"A weather log has one day per line in `weather.csv`: the date, the rain in millimeters and the day's highest temperature, like `May 1,0,17`. `Day.java` and `Main.java` are ready: `main` reads the file into a `WeatherLog` and asks it three questions. In `WeatherLog.java`, replace the placeholders:\n\n- `add(Day day)` adds the day to the list.\n- `rainyDays()` returns how many days had some rain (more than 0 mm).\n- `totalRain()` returns the rain of all the days added up.\n- `warmestDryDay()` returns the day with the highest temperature among the days with no rain, or `null` when there's no dry day. If two are equally warm, the first one.\n\nWeatherLog doesn't print anything. For the file shown below, the program prints:\n\n```\nRainy days: 3\nRain in all: 21 mm\nWarmest dry day: May 4 (19 degrees, 0 mm)\n```\n","kind":"code","seed":"// ==== WeatherLog.java ====\nimport java.util.ArrayList;\n\npublic class WeatherLog {\n    private ArrayList<Day> days;\n\n    public WeatherLog() {\n        this.days = new ArrayList<>();\n    }\n\n    public void add(Day day) {\n        // Replace the placeholders with the real code.\n    }\n\n    public int rainyDays() {\n        return 0;\n    }\n\n    public int totalRain() {\n        return 0;\n    }\n\n    public Day warmestDryDay() {\n        return null;\n    }\n}\n\n// ==== Day.java ====\npublic class Day {\n    private String date;\n    private int rain;\n    private int temperature;\n\n    public Day(String date, int rain, int temperature) {\n        this.date = date;\n        this.rain = rain;\n        this.temperature = temperature;\n    }\n\n    public int getRain() {\n        return this.rain;\n    }\n\n    public int getTemperature() {\n        return this.temperature;\n    }\n\n    public String toString() {\n        return this.date + \" (\" + this.temperature + \" degrees, \" + this.rain + \" mm)\";\n    }\n}\n\n// ==== Main.java ====\nimport java.nio.file.Paths;\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        WeatherLog log = new WeatherLog();\n        try (Scanner scanner = new Scanner(Paths.get(\"weather.csv\"))) {\n            while (scanner.hasNextLine()) {\n                String[] parts = scanner.nextLine().split(\",\");\n                if (parts.length < 3) {\n                    continue;\n                }\n                log.add(new Day(parts[0], Integer.valueOf(parts[1]), Integer.valueOf(parts[2])));\n            }\n        } catch (Exception e) {\n            System.out.println(\"Error: \" + e.getMessage());\n        }\n        System.out.println(\"Rainy days: \" + log.rainyDays());\n        System.out.println(\"Rain in all: \" + log.totalRain() + \" mm\");\n        Day warmest = log.warmestDryDay();\n        if (warmest == null) {\n            System.out.println(\"No dry days\");\n        } else {\n            System.out.println(\"Warmest dry day: \" + warmest);\n        }\n    }\n}\n","solution":"// ==== WeatherLog.java ====\nimport java.util.ArrayList;\n\npublic class WeatherLog {\n    private ArrayList<Day> days;\n\n    public WeatherLog() {\n        this.days = new ArrayList<>();\n    }\n\n    public void add(Day day) {\n        this.days.add(day);\n    }\n\n    public int rainyDays() {\n        int count = 0;\n        for (Day day : this.days) {\n            if (day.getRain() > 0) {\n                count++;\n            }\n        }\n        return count;\n    }\n\n    public int totalRain() {\n        int total = 0;\n        for (Day day : this.days) {\n            total += day.getRain();\n        }\n        return total;\n    }\n\n    public Day warmestDryDay() {\n        Day warmest = null;\n        for (Day day : this.days) {\n            if (day.getRain() > 0) {\n                continue;\n            }\n            if (warmest == null || day.getTemperature() > warmest.getTemperature()) {\n                warmest = day;\n            }\n        }\n        return warmest;\n    }\n}\n\n// ==== Day.java ====\npublic class Day {\n    private String date;\n    private int rain;\n    private int temperature;\n\n    public Day(String date, int rain, int temperature) {\n        this.date = date;\n        this.rain = rain;\n        this.temperature = temperature;\n    }\n\n    public int getRain() {\n        return this.rain;\n    }\n\n    public int getTemperature() {\n        return this.temperature;\n    }\n\n    public String toString() {\n        return this.date + \" (\" + this.temperature + \" degrees, \" + this.rain + \" mm)\";\n    }\n}\n\n// ==== Main.java ====\nimport java.nio.file.Paths;\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        WeatherLog log = new WeatherLog();\n        try (Scanner scanner = new Scanner(Paths.get(\"weather.csv\"))) {\n            while (scanner.hasNextLine()) {\n                String[] parts = scanner.nextLine().split(\",\");\n                if (parts.length < 3) {\n                    continue;\n                }\n                log.add(new Day(parts[0], Integer.valueOf(parts[1]), Integer.valueOf(parts[2])));\n            }\n        } catch (Exception e) {\n            System.out.println(\"Error: \" + e.getMessage());\n        }\n        System.out.println(\"Rainy days: \" + log.rainyDays());\n        System.out.println(\"Rain in all: \" + log.totalRain() + \" mm\");\n        Day warmest = log.warmestDryDay();\n        if (warmest == null) {\n            System.out.println(\"No dry days\");\n        } else {\n            System.out.println(\"Warmest dry day: \" + warmest);\n        }\n    }\n}\n","hints":["`rainyDays` is a count with a condition, `day.getRain() > 0`, and `totalRain` adds up `day.getRain()` for every day.","`warmestDryDay` is a best match, like `cheapestFor` in the lesson: skip the rainy days with `continue`, and keep the warmest of the rest, starting from `null`. Use `>`, so that the first of two equally warm days stays."],"tests":[{"name":"Test 1","stdin":"","files":{"weather.csv":"May 1,0,17\nMay 2,6,14\nMay 3,12,11\nMay 4,0,19\nMay 5,3,16\nMay 6,0,18\n"},"expect":"Rainy days: 3\nRain in all: 21 mm\nWarmest dry day: May 4 (19 degrees, 0 mm)","hidden":false},{"name":"Test 2","stdin":"","files":{"weather.csv":"June 1,4,21\nJune 2,9,20\n"},"expect":"Rainy days: 2\nRain in all: 13 mm\nNo dry days","hidden":true},{"name":"Test 3","stdin":"","files":{"weather.csv":"July 1,0,24\nJuly 2,0,26\nJuly 3,0,26\nJuly 4,1,30\n"},"expect":"Rainy days: 1\nRain in all: 1 mm\nWarmest dry day: July 2 (26 degrees, 0 mm)","hidden":true},{"name":"Test 4","stdin":"","call":"WeatherLog log = new WeatherLog();\nSystem.out.println(log.rainyDays() + \" \" + log.totalRain() + \" \" + log.warmestDryDay());\nlog.add(new Day(\"Mon\", 0, -2));\nlog.add(new Day(\"Tue\", 5, 3));\nSystem.out.println(log.rainyDays() + \" \" + log.totalRain() + \" \" + log.warmestDryDay());\n","expect":"0 0 null\n1 5 Mon (-2 degrees, 0 mm)","hidden":true}],"require":[],"forbid":[{"pattern":"\\bclass\\s+WeatherLog\\b[\\s\\S]*\\bSystem\\s*\\.\\s*out\\b[\\s\\S]*\\bclass\\s+Day\\b","message":"WeatherLog doesn't print. Its methods return their answers, and `main` prints them."}]}]}