{"id":"streams-input","slug":"from-input","title":"Streams of input","text":"The streams so far start from a list, and input comes one line at a time. So a program that works on input with a stream first reads the lines into a list, with the loop you know from module 13, and then makes a stream of the list.\n\n### Read first, then stream\n\nA weather station sends its temperature readings, in whole degrees, one per line. An empty line ends them:\n\n```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<String> lines = new ArrayList<>();\n        while (true) {\n            String line = scanner.nextLine();\n            if (line.isEmpty()) {\n                break;\n            }\n            lines.add(line);\n        }\n\n        long belowZero = lines.stream()\n                .mapToInt(Integer::valueOf)\n                .filter(degrees -> degrees < 0)\n                .count();\n        long aboveZero = lines.stream()\n                .mapToInt(Integer::valueOf)\n                .filter(degrees -> degrees > 0)\n                .count();\n        System.out.println(\"Readings: \" + lines.size());\n        System.out.println(\"Below zero: \" + belowZero);\n        System.out.println(\"Above zero: \" + aboveZero);\n    }\n}\n```\n\n```input\n-3\n2\n5\n-1\n0\n4\n\n```\n\n```output\nReadings: 6\nBelow zero: 2\nAbove zero: 3\n```\n\n- The loop only reads and stores. It doesn't count or add anything: that's the streams' work.\n- `mapToInt(Integer::valueOf)` turns each line into an `int`: `Integer::valueOf` is step 2's method reference for `line -> Integer.valueOf(line)`.\n- An IntStream has `filter` too, and `count`, so the filter can come after `mapToInt` and compare plain numbers.\n\n### A list of numbers\n\nWhen a program asks several questions about the same numbers, it can turn the lines into numbers once, with `map(Integer::valueOf)` and `collect`, and then stream the list of numbers as often as it likes. Before an average, it checks that there's at least one reading, since `getAsDouble()` crashes when there's no value (step 3):\n\n```java\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Scanner;\nimport java.util.stream.Collectors;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        ArrayList<String> lines = new ArrayList<>();\n        while (true) {\n            String line = scanner.nextLine();\n            if (line.isEmpty()) {\n                break;\n            }\n            lines.add(line);\n        }\n\n        List<Integer> readings = lines.stream()\n                .map(Integer::valueOf)\n                .collect(Collectors.toList());\n        if (readings.isEmpty()) {\n            System.out.println(\"No readings\");\n        } else {\n            double average = readings.stream()\n                    .mapToInt(degrees -> degrees)\n                    .average()\n                    .getAsDouble();\n            System.out.println(\"Average: \" + average);\n            System.out.println(\"Frosty: \" + readings.stream().filter(degrees -> degrees <= 0).count());\n        }\n    }\n}\n```\n\n```input\n-3\n2\n5\n-1\n0\n4\n\n```\n\n```output\nAverage: 1.1666666666666667\nFrosty: 3\n```\n\nThe average is 7 / 6, printed with every digit a double has, as in module 29.\n\n### Lines that aren't numbers\n\nWhen the station misses a reading, it sends a `-` in its place. `Integer.valueOf(\"-\")` crashes, so a `filter` must drop those lines, and it must come *before* the step that turns lines into numbers. A filter after `mapToInt` comes too late, whatever it keeps, because the crash comes first:\n\n```java\nimport java.util.ArrayList;\n\npublic class Main {\n    public static void main(String[] args) {\n        ArrayList<String> lines = new ArrayList<>();\n        lines.add(\"4\");\n        lines.add(\"-\");\n        lines.add(\"6\");\n        int sum = lines.stream()\n                .mapToInt(Integer::valueOf)\n                .filter(degrees -> degrees > 0)\n                .sum();\n        System.out.println(sum);\n    }\n}\n```\n\n```crash\nException in thread \"main\" java.lang.NumberFormatException: For input string: \"-\"\n\tat Main.main(Main.java:12)\n```\n\nJava reports the line where the chain's terminal operation, `sum()`, runs, because that's when the lambdas and method references are called. The dash filter itself can't go after `mapToInt` at all: by then the values are `int`s, which have no `equals`. So filter the dashes out first, `.filter(line -> !line.equals(\"-\"))`, and then turn the rest into numbers: the sum is 10.\n","task":"The station's rain gauge sends the rain of each hour, in whole millimeters, one per line, and an empty line ends them. Read the lines into a list, and then use streams to print how many hours there were, how many of them were dry (0 mm), and the total rain. For the input shown, the program prints:\n\n```\nHours: 6\nDry hours: 2\nTotal rain: 9 mm\n```\n","kind":"code","seed":"import 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<String> lines = new ArrayList<>();\n\n    }\n}\n","solution":"import 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<String> lines = new ArrayList<>();\n        while (true) {\n            String line = scanner.nextLine();\n            if (line.isEmpty()) {\n                break;\n            }\n            lines.add(line);\n        }\n\n        long dry = lines.stream()\n                .mapToInt(Integer::valueOf)\n                .filter(mm -> mm == 0)\n                .count();\n        int total = lines.stream()\n                .mapToInt(Integer::valueOf)\n                .sum();\n        System.out.println(\"Hours: \" + lines.size());\n        System.out.println(\"Dry hours: \" + dry);\n        System.out.println(\"Total rain: \" + total + \" mm\");\n    }\n}\n","hints":["Read with a `while (true)` loop: `nextLine()`, `break` on an empty line, and `lines.add(line)` otherwise. The number of hours is the list's size.","`lines.stream().mapToInt(Integer::valueOf).filter(mm -> mm == 0).count()` counts the dry hours, and `lines.stream().mapToInt(Integer::valueOf).sum()` adds up the rain."],"tests":[{"name":"Test 1","stdin":"0\n2\n5\n0\n1\n1\n\n","expect":"Hours: 6\nDry hours: 2\nTotal rain: 9 mm","hidden":false},{"name":"Test 2","stdin":"3\n\n","expect":"Hours: 1\nDry hours: 0\nTotal rain: 3 mm","hidden":true},{"name":"Test 3","stdin":"0\n0\n0\n12\n0\n\n","expect":"Hours: 5\nDry hours: 4\nTotal rain: 12 mm","hidden":true},{"name":"Test 4","stdin":"\n","expect":"Hours: 0\nDry hours: 0\nTotal rain: 0 mm","hidden":true}],"require":[{"pattern":"\\.stream\\s*\\(\\s*\\)","message":"Count and add up with streams: `lines.stream()`."},{"pattern":"\\.sum\\s*\\(\\s*\\)","message":"Add up the rain with `sum()`."},{"pattern":"\\.count\\s*\\(\\s*\\)","message":"Count the dry hours with `count()`."}],"forbid":[{"pattern":"\\bfor\\b","message":"The `while` loop reads the lines; count and add up with streams, not with a `for` loop."}],"more":[{"task":"The station sends `-` for a reading it missed. Fill in the blank, a method reference that turns a line into a number, so that the program prints the average of the real readings. The average is the stream's own `average()`: the sum divided by the count. For the input shown, it prints:\n\n```\nAverage: 2.5\n```\n","kind":"fill","seed":"import 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<String> lines = new ArrayList<>();\n        while (true) {\n            String line = scanner.nextLine();\n            if (line.isEmpty()) {\n                break;\n            }\n            lines.add(line);\n        }\n\n        double average = lines.stream()\n                .filter(line -> !line.equals(\"-\"))\n                .mapToInt([[Integer::valueOf‖Integer::parseInt]])\n                .average()\n                .getAsDouble();\n        System.out.println(\"Average: \" + average);\n    }\n}\n","solution":"import 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<String> lines = new ArrayList<>();\n        while (true) {\n            String line = scanner.nextLine();\n            if (line.isEmpty()) {\n                break;\n            }\n            lines.add(line);\n        }\n\n        double average = lines.stream()\n                .filter(line -> !line.equals(\"-\"))\n                .mapToInt(Integer::valueOf)\n                .average()\n                .getAsDouble();\n        System.out.println(\"Average: \" + average);\n    }\n}\n","hints":["`mapToInt` needs something that turns a String into a number. As a method reference, that's the class's name, `::`, and the method's name, with no parentheses.","`Integer::valueOf` is short for `line -> Integer.valueOf(line)`."],"tests":[{"name":"Test 1","stdin":"4\n-\n-3\n6\n-\n3\n\n","expect":"Average: 2.5","hidden":false},{"name":"Test 2","stdin":"-\n7\n\n","expect":"Average: 7.0","hidden":true}],"require":[],"forbid":[]},{"task":"A humidity sensor sends readings, in whole percent, one per line, until the word `end`. Print the different readings of 90 or more, as a list from the smallest up, each once. Read the lines into a list first, then use one stream with `map`, `filter`, `distinct`, `sorted` and `collect`. For the input shown, the program prints:\n\n```\nHumid readings: [90, 94, 97]\n```\n","kind":"code","seed":"import java.util.ArrayList;\nimport java.util.List;\nimport java.util.Scanner;\nimport java.util.stream.Collectors;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        ArrayList<String> lines = new ArrayList<>();\n\n    }\n}\n","solution":"import java.util.ArrayList;\nimport java.util.List;\nimport java.util.Scanner;\nimport java.util.stream.Collectors;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        ArrayList<String> lines = new ArrayList<>();\n        while (true) {\n            String line = scanner.nextLine();\n            if (line.equals(\"end\")) {\n                break;\n            }\n            lines.add(line);\n        }\n\n        List<Integer> humid = lines.stream()\n                .map(Integer::valueOf)\n                .filter(percent -> percent >= 90)\n                .distinct()\n                .sorted()\n                .collect(Collectors.toList());\n        System.out.println(\"Humid readings: \" + humid);\n    }\n}\n","hints":["Read until the line is `end`, and add every other line to the list. Then turn the lines into numbers with `map(Integer::valueOf)`: sorting Strings would put `100` before `90`.","After `map`, `filter(percent -> percent >= 90)`, `distinct()` and `sorted()`, and end with `collect(Collectors.toList())`."],"tests":[{"name":"Test 1","stdin":"88\n94\n90\n97\n72\n94\nend\n","expect":"Humid readings: [90, 94, 97]","hidden":false},{"name":"Test 2","stdin":"100\n95\n89\n100\nend\n","expect":"Humid readings: [95, 100]","hidden":true},{"name":"Test 3","stdin":"45\nend\n","expect":"Humid readings: []","hidden":true}],"require":[{"pattern":"\\.map\\s*\\(","message":"Turn the lines into numbers with `map(Integer::valueOf)`."},{"pattern":"\\.distinct\\s*\\(\\s*\\)","message":"Keep each reading once with `distinct()`."},{"pattern":"\\.sorted\\s*\\(\\s*\\)","message":"Sort with `sorted()`."},{"pattern":"\\.collect\\s*\\(\\s*Collectors\\s*\\.\\s*toList\\s*\\(\\s*\\)\\s*\\)","message":"Gather the readings with `collect(Collectors.toList())`."}],"forbid":[{"pattern":"\\bfor\\b","message":"The `while` loop reads the lines; do the rest with a stream, not a `for` loop."}]}]}