{"id":"reading-files-records","slug":"records-in-a-file","title":"Records in a file","text":"Most data files hold **records**: each line describes one thing, with its values separated by a comma. This format is called CSV, short for comma-separated values, and spreadsheet programs can save a sheet in it. Each line is read, split and converted, just as in module 16:\n\n```file rentals.txt\nIlona,3\nMarek,1\nSofia,5\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        int total = 0;\n        int longest = 0;\n        String longestName = \"\";\n        try (Scanner scanner = new Scanner(Paths.get(\"rentals.txt\"))) {\n            while (scanner.hasNextLine()) {\n                String[] parts = scanner.nextLine().split(\",\");\n                String name = parts[0];\n                int hours = Integer.valueOf(parts[1]);\n                System.out.println(name + \": \" + hours + \" h\");\n                total += hours;\n                if (hours > longest) {\n                    longest = hours;\n                    longestName = name;\n                }\n            }\n        } catch (Exception e) {\n            System.out.println(\"Error: \" + e.getMessage());\n        }\n        System.out.println(\"Total: \" + total + \" h\");\n        System.out.println(\"Longest rental: \" + longestName);\n    }\n}\n```\n\n```output\nIlona: 3 h\nMarek: 1 h\nSofia: 5 h\nTotal: 9 h\nLongest rental: Sofia\n```\n\n`scanner.nextLine().split(\",\")` reads a line and splits it at once. The parts are Strings, so the hours need `Integer.valueOf` before they can be added up.\n\nNot every file uses commas. Semicolons are common too, especially where the comma is the decimal separator: then the line is split with `split(\";\")`. Look at the file before you write the program.\n\n### A header line\n\nA CSV file often starts with a **header**: a first line that names the columns, such as `name,hours`. It isn't data, and `Integer.valueOf(\"hours\")` would fail. Read it before the loop, and don't use it:\n\n```java\ntry (Scanner scanner = new Scanner(Paths.get(\"rentals.csv\"))) {\n    scanner.nextLine(); // skip the header\n    while (scanner.hasNextLine()) {\n        String[] parts = scanner.nextLine().split(\",\");\n        // ...\n    }\n}\n```\n\n### Lines that don't fit\n\nA line with no comma splits into one part only, and `parts[1]` throws an exception. Inside the `try`, that ends the reading. When a file may have such lines, check `parts.length` first, and skip the line with `continue` if it has too few parts.\n","task":"The file `stock.txt` has lines such as `pens;40`: an item and how many are left in stock, separated by a semicolon. Fill in the blanks, so the program prints the items that have fewer than 10 left. For the file shown below, it prints:\n\n```\nLow on stock:\nglue (4)\ntape (0)\n```\n","kind":"fill","seed":"import java.nio.file.Paths;\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        System.out.println(\"Low on stock:\");\n        try (Scanner scanner = new Scanner(Paths.get(\"stock.txt\"))) {\n            while (scanner.hasNextLine()) {\n                String[] parts = scanner.nextLine().split(\"[[;]]\");\n                int count = Integer.[[valueOf‖parseInt]](parts[1]);\n                if (count < 10) {\n                    System.out.println(parts[0] + \" (\" + count + \")\");\n                }\n            }\n        } catch (Exception e) {\n            System.out.println(\"Error: \" + e.getMessage());\n        }\n    }\n}\n","solution":"import java.nio.file.Paths;\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        System.out.println(\"Low on stock:\");\n        try (Scanner scanner = new Scanner(Paths.get(\"stock.txt\"))) {\n            while (scanner.hasNextLine()) {\n                String[] parts = scanner.nextLine().split(\";\");\n                int count = Integer.valueOf(parts[1]);\n                if (count < 10) {\n                    System.out.println(parts[0] + \" (\" + count + \")\");\n                }\n            }\n        } catch (Exception e) {\n            System.out.println(\"Error: \" + e.getMessage());\n        }\n    }\n}\n","hints":["Look at the file: the item and the count are separated by `;`.","The count is text until `Integer.valueOf(parts[1])` makes it an `int`."],"tests":[{"name":"Test 1","stdin":"","files":{"stock.txt":"pens;40\nglue;4\npaper;120\ntape;0\n"},"expect":"Low on stock:\nglue (4)\ntape (0)","hidden":false},{"name":"Test 2","stdin":"","files":{"stock.txt":"staples;9\nfolders;10\nmarkers;2\n"},"expect":"Low on stock:\nstaples (9)\nmarkers (2)","hidden":true}],"require":[],"forbid":[],"more":[{"task":"The file `order.csv` starts with the header line `item,quantity,price`, and each line after it is one row of an order, such as `notebook,3,4`: 3 notebooks at 4 euros each. Print each item with its row total (quantity times price), and then the total of the whole order. For the file shown below, the program prints:\n\n```\nnotebook: 12\npencil: 5\nbackpack: 25\nTotal: 42\n```\n","kind":"code","seed":"import java.nio.file.Paths;\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n\n    }\n}\n","solution":"import java.nio.file.Paths;\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        int total = 0;\n        try (Scanner scanner = new Scanner(Paths.get(\"order.csv\"))) {\n            scanner.nextLine();\n            while (scanner.hasNextLine()) {\n                String[] parts = scanner.nextLine().split(\",\");\n                int quantity = Integer.valueOf(parts[1]);\n                int price = Integer.valueOf(parts[2]);\n                int rowTotal = quantity * price;\n                System.out.println(parts[0] + \": \" + rowTotal);\n                total += rowTotal;\n            }\n        } catch (Exception e) {\n            System.out.println(\"Error: \" + e.getMessage());\n        }\n        System.out.println(\"Total: \" + total);\n    }\n}\n","hints":["Skip the header with one `scanner.nextLine();` before the loop.","Each line splits into three parts: the item is `parts[0]`, the quantity `parts[1]` and the price `parts[2]`. Convert the last two with `Integer.valueOf`."],"tests":[{"name":"Test 1","stdin":"","files":{"order.csv":"item,quantity,price\nnotebook,3,4\npencil,5,1\nbackpack,1,25\n"},"expect":"notebook: 12\npencil: 5\nbackpack: 25\nTotal: 42","hidden":false},{"name":"Test 2","stdin":"","files":{"order.csv":"item,quantity,price\neraser,2,1\nruler,4,2\n"},"expect":"eraser: 2\nruler: 8\nTotal: 10","hidden":true},{"name":"Test 3","stdin":"","files":{"order.csv":"item,quantity,price\ncalculator,1,18\n"},"expect":"calculator: 18\nTotal: 18","hidden":true}],"require":[],"forbid":[]},{"task":"What does this program print? Every line is split, and then each of its parts is added.\n","kind":"predict","seed":"import java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(\"7,8\\n10,5,1\\n4\");\n        while (scanner.hasNextLine()) {\n            String[] parts = scanner.nextLine().split(\",\");\n            int sum = 0;\n            for (String part : parts) {\n                sum += Integer.valueOf(part);\n            }\n            System.out.println(\"Parts: \" + parts.length + \", sum: \" + sum);\n        }\n    }\n}\n","solution":"import java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(\"7,8\\n10,5,1\\n4\");\n        while (scanner.hasNextLine()) {\n            String[] parts = scanner.nextLine().split(\",\");\n            int sum = 0;\n            for (String part : parts) {\n                sum += Integer.valueOf(part);\n            }\n            System.out.println(\"Parts: \" + parts.length + \", sum: \" + sum);\n        }\n    }\n}\n","hints":["The String holds three lines: `7,8`, `10,5,1` and `4`.","A line without a comma splits into one part: the whole line."],"tests":[{"name":"Output","stdin":"","expect":"Parts: 2, sum: 15\nParts: 3, sum: 16\nParts: 1, sum: 4","hidden":false}],"require":[],"forbid":[],"lines":["Parts: 2, sum: 15","Parts: 3, sum: 16","Parts: 1, sum: 4"]}]}