{"id":"charts-rows-to-numbers","slug":"rows-to-numbers","title":"From text rows to numbers","text":"Part 14 starts with **data visualization**: showing numbers as a picture. A table of 50 numbers is hard to read, while a line that climbs and then falls tells its story at a glance. JavaFX, the library of part 13, has classes for charts, and this module uses them: line charts, bar charts and charts that change when the user moves a slider.\n\n### Charts in Java Arena\n\nAs with the windows of part 13 (module 48 explained it), Java Arena's JavaFX is a practice version written for this site, and the page draws each chart from what your program told JavaFX to show: the kind of chart, its title, its axes and every data point. The drawing is approximate, as the windows were: real JavaFX picks its own colors, fonts and tick marks. Under each chart, \"The window as text\" lists every data point, and that's what the checks compare. The check that runs every model solution on the real JavaFX (module 48) covers charts too: it confirms that real JavaFX gets the same kind of chart, title, legend, axes and points, and the same slider values. It doesn't check how the chart is drawn. (Module 52 draws on a canvas and works with images, which are read and written pixel by pixel exactly; it also explains why sound can't play on this page.)\n\nA chart needs numbers, and numbers usually come from a file. So this first step reads files of data, and step 2 draws the first chart. The numbers in this module's files are made up for the exercises: they look like measurements, but nobody measured them.\n\n### Rows and columns\n\nA weather station on a hill measures the rain each month. Its file, `rain.csv`:\n\n```file rain.csv\nmonth,mm\n1,52\n2,41\n3,-\n4,38\n5,45\n6,60\n```\n\nThis is a **CSV** file (comma-separated values): one **row** per line, and the values of a row separated by commas, so that they form **columns**. The first line is a **header**: it names the columns, and it isn't data. The `-` in March means that there's no measurement for that month (the rain gauge was broken). Files of data often mark a missing value like that, so a program must expect it.\n\nThe program reads the lines (module 48 used `Files.readAllLines` too), skips the header, and splits each row into its values with `split`:\n\n```java\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\nimport java.util.List;\n\npublic class Main {\n    public static void main(String[] args) throws Exception {\n        List<String> lines = Files.readAllLines(Paths.get(\"rain.csv\"));\n        int total = 0;\n        int months = 0;\n        for (int i = 1; i < lines.size(); i++) {\n            String[] parts = lines.get(i).split(\",\");\n            if (parts[1].equals(\"-\")) {\n                System.out.println(\"Month \" + parts[0] + \": no measurement\");\n            } else {\n                total += Integer.valueOf(parts[1]);\n                months++;\n            }\n        }\n        System.out.println(\"Months measured: \" + months);\n        System.out.println(\"Rain in them: \" + total + \" mm\");\n    }\n}\n```\n\n```output\nMonth 3: no measurement\nMonths measured: 5\nRain in them: 236 mm\n```\n\n- The loop starts at index 1, so the header line is left out. `Integer.valueOf(\"mm\")` would throw a NumberFormatException.\n- `\"4,38\".split(\",\")` gives the array `[\"4\", \"38\"]`: text, still. `Integer.valueOf(parts[1])` turns the text into a number. A value with a decimal point, such as `12.5`, needs `Double.valueOf`.\n- The check for `-` comes **before** `Integer.valueOf`, since `Integer.valueOf(\"-\")` throws too. A missing value is skipped: it isn't counted as 0, which would be a measurement of no rain at all, and so a lie.\n\nTwo more things that files of data do:\n\n- **Tabs.** A **tab-separated** file (often named `.tsv`) has a tab between its values instead of a comma: `split(\"\\t\")`. Tabs are handy when the values themselves may contain commas, such as `Smith, Ann`.\n- **Empty values at the end.** `split` drops empty values at the end of a row: `\"5,\".split(\",\")` gives only `[\"5\"]`, an array of length 1, and `parts[1]` throws an ArrayIndexOutOfBoundsException. That's one reason why files mark a missing value with a `-` rather than with nothing.\n\n### Keeping the numbers\n\nA sum is fine for a report, but a chart needs every value, and which month it belongs to. A **map** from the month to the rain keeps both, and a month without a measurement simply isn't in it. Put the reading in a class of its own, and the rest of the program asks it for the map. The same file again:\n\n```file rain.csv\nmonth,mm\n1,52\n2,41\n3,-\n4,38\n5,45\n6,60\n```\n\n```java\nimport java.util.Map;\n\npublic class Main {\n    public static void main(String[] args) throws Exception {\n        Map<Integer, Integer> rain = RainFile.read(\"rain.csv\");\n        System.out.println(rain.size() + \" months\");\n        System.out.println(\"April: \" + rain.get(4) + \" mm\");\n        System.out.println(\"March measured: \" + rain.containsKey(3));\n    }\n}\n// ==== RainFile.java ====\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\npublic class RainFile {\n\n    public static Map<Integer, Integer> read(String fileName) throws IOException {\n        Map<Integer, Integer> rain = new HashMap<>();\n        List<String> lines = Files.readAllLines(Paths.get(fileName));\n        for (int i = 1; i < lines.size(); i++) {\n            String[] parts = lines.get(i).split(\",\");\n            if (!parts[1].equals(\"-\")) {\n                rain.put(Integer.valueOf(parts[0]), Integer.valueOf(parts[1]));\n            }\n        }\n        return rain;\n    }\n}\n```\n\n```output\n5 months\nApril: 38 mm\nMarch measured: false\n```\n\n`read` declares `throws IOException` (module 42), since `Files.readAllLines` can throw it, and leaves it to the caller. When the values are only a sequence, such as the cups of coffee on each day of a week in order, a **list** is enough: the position in the list says where each value belongs.\n","task":"The school library counts its loans each month, in the tab-separated file `loans.tsv`. The library was closed for a month, so that month has a `-`. Fill in the blanks, so that the program prints each month with its loans (or `closed`), then the busiest month:\n\n```text\nmonth\tloans\nJan\t412\nFeb\t388\nMar\t455\nApr\t-\nMay\t301\n```\n\nThe program should print:\n\n```\nJan: 412\nFeb: 388\nMar: 455\nApr: closed\nMay: 301\nBusiest month: Mar\n```\n","kind":"fill","seed":"import java.nio.file.Files;\nimport java.nio.file.Paths;\nimport java.util.List;\n\npublic class Main {\n    public static void main(String[] args) throws Exception {\n        List<String> lines = Files.readAllLines(Paths.get(\"loans.tsv\"));\n        String busiest = \"\";\n        int most = 0;\n        for (int i = [[1]]; i < lines.size(); i++) {\n            String[] parts = lines.get(i).split(\"[[\\t]]\");\n            if (parts[1].equals(\"-\")) {\n                System.out.println(parts[0] + \": closed\");\n            } else {\n                int loans = Integer.[[valueOf‖parseInt]](parts[1]);\n                System.out.println(parts[0] + \": \" + loans);\n                if (loans > most) {\n                    most = loans;\n                    busiest = parts[0];\n                }\n            }\n        }\n        System.out.println(\"Busiest month: \" + busiest);\n    }\n}\n","solution":"import java.nio.file.Files;\nimport java.nio.file.Paths;\nimport java.util.List;\n\npublic class Main {\n    public static void main(String[] args) throws Exception {\n        List<String> lines = Files.readAllLines(Paths.get(\"loans.tsv\"));\n        String busiest = \"\";\n        int most = 0;\n        for (int i = 1; i < lines.size(); i++) {\n            String[] parts = lines.get(i).split(\"\\t\");\n            if (parts[1].equals(\"-\")) {\n                System.out.println(parts[0] + \": closed\");\n            } else {\n                int loans = Integer.valueOf(parts[1]);\n                System.out.println(parts[0] + \": \" + loans);\n                if (loans > most) {\n                    most = loans;\n                    busiest = parts[0];\n                }\n            }\n        }\n        System.out.println(\"Busiest month: \" + busiest);\n    }\n}\n","hints":["The first line of the file is the header. Which index does the loop start from to skip it? Which character separates the values in a `.tsv` file, and which method of `Integer` turns text into a number?","The loop starts at index 1, after the header. The values are separated by a tab, written `\\t` inside a string. `Integer.valueOf` turns a text of digits into a number."],"tests":[{"name":"Test 1","stdin":"","files":{"loans.tsv":"month\tloans\nJan\t412\nFeb\t388\nMar\t455\nApr\t-\nMay\t301\n"},"expect":"Jan: 412\nFeb: 388\nMar: 455\nApr: closed\nMay: 301\nBusiest month: Mar","hidden":false},{"name":"Test 2","stdin":"","files":{"loans.tsv":"month\tloans\nSep\t520\nOct\t-\nNov\t610\nDec\t240\n"},"expect":"Sep: 520\nOct: closed\nNov: 610\nDec: 240\nBusiest month: Nov","hidden":true},{"name":"Test 3","stdin":"","files":{"loans.tsv":"month\tloans\nJun\t-\nJul\t-\nAug\t95\n"},"expect":"Jun: closed\nJul: closed\nAug: 95\nBusiest month: Aug","hidden":true}],"require":[],"forbid":[],"more":[{"task":"A running club logs how far its training group ran each week, in kilometers, in `weeks.csv`. A week without a training run has a `-`:\n\n```text\nweek,km\n1,24.5\n2,-\n3,31\n4,28.5\n```\n\nWrite the class `DistanceLog` in `DistanceLog.java`, with the method `public static Map<Integer, Double> read(String fileName) throws IOException`. It reads the file (the header first, then one row per week) and returns a map from each week's number to its kilometers, as a double. A week with a `-` isn't in the map at all.\n\n`Main.java` is a test drive. The check runs:\n\n```java\nMap<Integer, Double> km = DistanceLog.read(\"weeks.csv\");\nSystem.out.println(km.size());\nSystem.out.println(km.get(1));\nSystem.out.println(km.get(3));\nSystem.out.println(km.containsKey(2));\n```\n\nand expects, for the file above:\n\n```\n3\n24.5\n31.0\nfalse\n```\n","kind":"code","seed":"// ==== DistanceLog.java ====\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\npublic class DistanceLog {\n\n}\n\n// ==== Main.java ====\nimport java.util.Map;\n\npublic class Main {\n    public static void main(String[] args) throws Exception {\n        Map<Integer, Double> km = DistanceLog.read(\"weeks.csv\");\n        System.out.println(km.size());\n        System.out.println(km.get(1));\n        System.out.println(km.get(3));\n        System.out.println(km.containsKey(2));\n    }\n}\n","solution":"// ==== DistanceLog.java ====\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\npublic class DistanceLog {\n\n    public static Map<Integer, Double> read(String fileName) throws IOException {\n        Map<Integer, Double> km = new HashMap<>();\n        List<String> lines = Files.readAllLines(Paths.get(fileName));\n        for (int i = 1; i < lines.size(); i++) {\n            String[] parts = lines.get(i).split(\",\");\n            if (!parts[1].equals(\"-\")) {\n                km.put(Integer.valueOf(parts[0]), Double.valueOf(parts[1]));\n            }\n        }\n        return km;\n    }\n}\n\n// ==== Main.java ====\nimport java.util.Map;\n\npublic class Main {\n    public static void main(String[] args) throws Exception {\n        Map<Integer, Double> km = DistanceLog.read(\"weeks.csv\");\n        System.out.println(km.size());\n        System.out.println(km.get(1));\n        System.out.println(km.get(3));\n        System.out.println(km.containsKey(2));\n    }\n}\n","hints":["Read the lines with `Files.readAllLines(Paths.get(fileName))` and go through them from index 1, which skips the header. Split each row with `split(\",\")`.","When `parts[1]` isn't `\"-\"`, put `Integer.valueOf(parts[0])` and `Double.valueOf(parts[1])` into the map. `Double.valueOf(\"31\")` is fine too: it gives 31.0."],"tests":[{"name":"Test 1","stdin":"","call":"Map<Integer, Double> km = DistanceLog.read(\"weeks.csv\");\nSystem.out.println(km.size());\nSystem.out.println(km.get(1));\nSystem.out.println(km.get(3));\nSystem.out.println(km.containsKey(2));\n","files":{"weeks.csv":"week,km\n1,24.5\n2,-\n3,31\n4,28.5\n"},"expect":"3\n24.5\n31.0\nfalse","hidden":false},{"name":"Test 2","stdin":"","call":"Map<Integer, Double> km = DistanceLog.read(\"weeks.csv\");\nSystem.out.println(km.size());\nSystem.out.println(km.get(7) + km.get(8));\nSystem.out.println(km.containsKey(5) + \" \" + km.containsKey(6) + \" \" + km.containsKey(9));\n","files":{"weeks.csv":"week,km\n5,-\n6,-\n7,40.25\n8,12\n9,33.5\n"},"expect":"3\n52.25\nfalse false true","hidden":true},{"name":"Test 3","stdin":"","call":"Map<Integer, Double> km = DistanceLog.read(\"weeks.csv\");\nSystem.out.println(km.isEmpty());\n","files":{"weeks.csv":"week,km\n"},"expect":"true","hidden":true}],"require":[],"forbid":[]},{"task":"A coffee machine in an office logs the cups it made each day. Some rows have a `-`, and one row lost its value altogether. What does this program print?\n","kind":"predict","seed":"import java.util.ArrayList;\n\npublic class Main {\n    public static void main(String[] args) {\n        String[] rows = {\"day,cups\", \"Mon,4\", \"Tue,-\", \"Wed,6\", \"Thu,\", \"Fri,5\"};\n        ArrayList<Integer> cups = new ArrayList<>();\n        int skipped = 0;\n        for (int i = 1; i < rows.length; i++) {\n            String[] parts = rows[i].split(\",\");\n            if (parts.length < 2 || parts[1].equals(\"-\")) {\n                skipped++;\n            } else {\n                cups.add(Integer.valueOf(parts[1]));\n            }\n        }\n        System.out.println(cups);\n        System.out.println(skipped + \" skipped\");\n        System.out.println(rows[0].split(\",\").length);\n    }\n}\n","solution":"import java.util.ArrayList;\n\npublic class Main {\n    public static void main(String[] args) {\n        String[] rows = {\"day,cups\", \"Mon,4\", \"Tue,-\", \"Wed,6\", \"Thu,\", \"Fri,5\"};\n        ArrayList<Integer> cups = new ArrayList<>();\n        int skipped = 0;\n        for (int i = 1; i < rows.length; i++) {\n            String[] parts = rows[i].split(\",\");\n            if (parts.length < 2 || parts[1].equals(\"-\")) {\n                skipped++;\n            } else {\n                cups.add(Integer.valueOf(parts[1]));\n            }\n        }\n        System.out.println(cups);\n        System.out.println(skipped + \" skipped\");\n        System.out.println(rows[0].split(\",\").length);\n    }\n}\n","hints":["The loop starts at index 1, so the header `day,cups` isn't one of the rows it reads. A row with `-` is skipped.","`\"Thu,\".split(\",\")` drops the empty value at the end, so its array has only one element, and `parts.length < 2` is true. The last line splits the header, which has two values."],"tests":[{"name":"Output","stdin":"","expect":"[4, 6, 5]\n2 skipped\n2","hidden":false}],"require":[],"forbid":[],"lines":["[4, 6, 5]","2 skipped","2"]}]}