Java Arena
Step 5 of 9

Streams of input

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.

Read first, then stream

A weather station sends its temperature readings, in whole degrees, one per line. An empty line ends them:

import java.util.ArrayList;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        ArrayList<String> lines = new ArrayList<>();
        while (true) {
            String line = scanner.nextLine();
            if (line.isEmpty()) {
                break;
            }
            lines.add(line);
        }

        long belowZero = lines.stream()
                .mapToInt(Integer::valueOf)
                .filter(degrees -> degrees < 0)
                .count();
        long aboveZero = lines.stream()
                .mapToInt(Integer::valueOf)
                .filter(degrees -> degrees > 0)
                .count();
        System.out.println("Readings: " + lines.size());
        System.out.println("Below zero: " + belowZero);
        System.out.println("Above zero: " + aboveZero);
    }
}
Input
-3
2
5
-1
0
4
(an empty line)
Output
Readings: 6
Below zero: 2
Above zero: 3
  • The loop only reads and stores. It doesn't count or add anything: that's the streams' work.
  • mapToInt(Integer::valueOf) turns each line into an int: Integer::valueOf is step 2's method reference for line -> Integer.valueOf(line).
  • An IntStream has filter too, and count, so the filter can come after mapToInt and compare plain numbers.

A list of numbers

When 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):

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        ArrayList<String> lines = new ArrayList<>();
        while (true) {
            String line = scanner.nextLine();
            if (line.isEmpty()) {
                break;
            }
            lines.add(line);
        }

        List<Integer> readings = lines.stream()
                .map(Integer::valueOf)
                .collect(Collectors.toList());
        if (readings.isEmpty()) {
            System.out.println("No readings");
        } else {
            double average = readings.stream()
                    .mapToInt(degrees -> degrees)
                    .average()
                    .getAsDouble();
            System.out.println("Average: " + average);
            System.out.println("Frosty: " + readings.stream().filter(degrees -> degrees <= 0).count());
        }
    }
}
Input
-3
2
5
-1
0
4
(an empty line)
Output
Average: 1.1666666666666667
Frosty: 3

The average is 7 / 6, printed with every digit a double has, as in module 29.

Lines that aren't numbers

When 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:

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList<String> lines = new ArrayList<>();
        lines.add("4");
        lines.add("-");
        lines.add("6");
        int sum = lines.stream()
                .mapToInt(Integer::valueOf)
                .filter(degrees -> degrees > 0)
                .sum();
        System.out.println(sum);
    }
}
The program crashes with
Exception in thread "main" java.lang.NumberFormatException: For input string: "-"
	at Main.main(Main.java:12)

Java 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 ints, 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.

Your task

Challenge 1 of 3

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:

Hours: 6
Dry hours: 2
Total rain: 9 mm

Challenge 2

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:

Average: 2.5

Challenge 3

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:

Humid readings: [90, 94, 97]