{"id":"larger-programs-statistics","slug":"statistics-from-input","title":"Statistics from input","text":"A common kind of program reads a series of numbers and then reports on them: how many there were, their average, the largest, and what share of them passed some limit. Before writing any code, decide three things:\n\n1. **What ends the input.** Here, a line with the word `end`.\n2. **Which values count.** Real input has mistakes in it, so write down a rule for what's valid, and what happens to the rest. Here, a line that breaks the rule is skipped: it's only counted as skipped.\n3. **What to print when nothing counts.** The average of no values would divide by zero, so that case gets a message of its own.\n\nA bowling league types in the scores of the evening's games, one per line. A game of bowling scores from 0 to 300 points, so any other number is a typing mistake:\n\n```java\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        int games = 0;\n        int skipped = 0;\n        int total = 0;\n        int best = 0;\n        int highGames = 0;\n        while (true) {\n            String line = scanner.nextLine();\n            if (line.equals(\"end\")) {\n                break;\n            }\n            int score = Integer.valueOf(line);\n            if (score < 0 || score > 300) {\n                skipped++;\n                continue;\n            }\n            games++;\n            total += score;\n            if (score > best) {\n                best = score;\n            }\n            if (score >= 200) {\n                highGames++;\n            }\n        }\n        System.out.println(\"Games: \" + games);\n        System.out.println(\"Skipped: \" + skipped);\n        if (games == 0) {\n            System.out.println(\"No games to report\");\n        } else {\n            double average = (double) total / games;\n            double highShare = 100.0 * highGames / games;\n            System.out.println(\"Average: \" + average);\n            System.out.println(\"Best: \" + best);\n            System.out.println(\"200 or more: \" + highShare + \"%\");\n        }\n    }\n}\n```\n\n```input\n156\n212\n310\n247\n131\n161\nend\n```\n\n```output\nGames: 5\nSkipped: 1\nAverage: 181.4\nBest: 247\n200 or more: 40.0%\n```\n\n- The loop reads each line as a String, so it can compare it with `end` before turning it into a number. The stop word is checked first, so it never becomes a score.\n- The rule is a single `if` near the top of the loop. Its `continue` skips the rest of the round, so a skipped value can't reach any of the counts below it: 310 is only counted in `skipped`.\n- Each result has its own variable, created before the loop: the number of games, the total (for the average), the best score, and the number of games of 200 or more (for the share).\n- `best` starts at 0. That works because no valid score is below 0. For values that can be negative, such as temperatures, 0 could be larger than all of them. Then take the first valid value as the best so far: `if (games == 1 || score > best)`.\n- With no valid games, `games` is 0, and the program says so instead of dividing by it.\n\n### Shares as percentages\n\nA share is the part divided by the whole, times 100. With two `int` values, the division drops the decimals (module 4), so the order matters. Start with `100.0`, a double, and the whole calculation keeps its decimals:\n\n```java\nint high = 2;\nint games = 5;\nSystem.out.println(high / games * 100);\nSystem.out.println(100 * high / games);\nSystem.out.println(100.0 * high / games);\n```\n\n```output\n0\n40\n40.0\n```\n\n`2 / 5` is 0 with `int` values, and 0 times 100 is still 0.\n\nA double can print many decimals, and two calculations that are the same on paper don't always give the same last digit. A double holds a value such as one third only approximately. Each step's result is rounded to the nearest double, so doing the same steps in another order can change the last digit:\n\n```java\nSystem.out.println(100.0 * 1 / 3);\nSystem.out.println((double) 1 / 3 * 100);\n```\n\n```output\n33.333333333333336\n33.33333333333333\n```\n\nBoth are one third of 100, and both are fine answers. But a check compares your output with the expected one character by character, so the tasks in this module say exactly which calculation to use. Use that one, and your output matches digit for digit.\n\n### The plan, for statistics\n\nStep 1's plan fits here too, in a smaller form. The data is plain numbers, so no data class is needed. The logic could be a class of its own that collects the numbers and answers the questions, like module 18's `TripLog`, which kept the count, the total and the longest of a series of bike trips. With a few questions, the variables in `main` are enough, as above. When the questions grow, move them into a class.\n","task":"At a charity run, the finishing times are typed in, in whole minutes, one per line, until `end`. A time must be from 15 to 180 minutes: other times are typing mistakes, and they're skipped. `fastest` starts at 180, the slowest valid time, so any valid time can take its place. Fill in the blanks. For the input shown, the program prints:\n\n```\nRunners: 4\nSkipped: 2\nAverage: 32.5 min\nFastest: 25 min\nUnder 30 min: 50.0%\n```\n","kind":"fill","seed":"import java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        int runners = 0;\n        int skipped = 0;\n        int total = 0;\n        int fastest = 180;\n        int underHalfHour = 0;\n        while (true) {\n            String line = scanner.nextLine();\n            if (line.equals(\"[[end]]\")) {\n                break;\n            }\n            int time = Integer.valueOf(line);\n            if (time < 15 [[||]] time > 180) {\n                skipped++;\n                continue;\n            }\n            runners++;\n            total += time;\n            if (time [[<‖<=]] fastest) {\n                fastest = time;\n            }\n            if (time < 30) {\n                underHalfHour++;\n            }\n        }\n        System.out.println(\"Runners: \" + runners);\n        System.out.println(\"Skipped: \" + skipped);\n        if (runners == 0) {\n            System.out.println(\"No runners\");\n        } else {\n            System.out.println(\"Average: \" + (double) total / runners + \" min\");\n            System.out.println(\"Fastest: \" + fastest + \" min\");\n            System.out.println(\"Under 30 min: \" + 100.0 * underHalfHour / [[runners]] + \"%\");\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(System.in);\n        int runners = 0;\n        int skipped = 0;\n        int total = 0;\n        int fastest = 180;\n        int underHalfHour = 0;\n        while (true) {\n            String line = scanner.nextLine();\n            if (line.equals(\"end\")) {\n                break;\n            }\n            int time = Integer.valueOf(line);\n            if (time < 15 || time > 180) {\n                skipped++;\n                continue;\n            }\n            runners++;\n            total += time;\n            if (time < fastest) {\n                fastest = time;\n            }\n            if (time < 30) {\n                underHalfHour++;\n            }\n        }\n        System.out.println(\"Runners: \" + runners);\n        System.out.println(\"Skipped: \" + skipped);\n        if (runners == 0) {\n            System.out.println(\"No runners\");\n        } else {\n            System.out.println(\"Average: \" + (double) total / runners + \" min\");\n            System.out.println(\"Fastest: \" + fastest + \" min\");\n            System.out.println(\"Under 30 min: \" + 100.0 * underHalfHour / runners + \"%\");\n        }\n    }\n}\n","hints":["The loop stops at the word `end`. A time is skipped when it's below 15 **or** above 180: no time can be both at once.","The fastest time is the smallest one, so a time replaces it when it's smaller. The share divides by the number of valid runners, not by every line read."],"tests":[{"name":"Test 1","stdin":"42\n28\n5\n35\n200\n25\nend\n","expect":"Runners: 4\nSkipped: 2\nAverage: 32.5 min\nFastest: 25 min\nUnder 30 min: 50.0%","hidden":false},{"name":"Test 2","stdin":"end\n","expect":"Runners: 0\nSkipped: 0\nNo runners","hidden":true},{"name":"Test 3","stdin":"15\n180\n14\n181\n29\nend\n","expect":"Runners: 3\nSkipped: 2\nAverage: 74.66666666666667 min\nFastest: 15 min\nUnder 30 min: 66.66666666666667%","hidden":true}],"require":[],"forbid":[],"more":[{"task":"At a fishing competition, the length of each fish caught is typed in, in whole centimeters, one per line. A `0` ends the input. The rule: a fish shorter than 25 cm is released back into the water, so it doesn't count in the average or the longest, but it's counted as caught and as released. Print:\n\n- `Fish caught: ` and the number of fish caught, kept and released together.\n- If at least one fish was caught: `Released: `, the share of released fish as a percentage, calculated as `100.0 * released / caught`, and `%`.\n- If at least one fish was kept: `Average length: `, the average length of the kept fish (calculated with `(double)`) and ` cm`, and then `Longest: `, the longest fish's length and ` cm`.\n\nFor the input shown, the program prints:\n\n```\nFish caught: 5\nReleased: 40.0%\nAverage length: 41.0 cm\nLongest: 52 cm\n```\n","kind":"code","seed":"import java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\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(System.in);\n        int caught = 0;\n        int released = 0;\n        int kept = 0;\n        int total = 0;\n        int longest = 0;\n        while (true) {\n            int length = Integer.valueOf(scanner.nextLine());\n            if (length == 0) {\n                break;\n            }\n            caught++;\n            if (length < 25) {\n                released++;\n                continue;\n            }\n            kept++;\n            total += length;\n            if (length > longest) {\n                longest = length;\n            }\n        }\n        System.out.println(\"Fish caught: \" + caught);\n        if (caught > 0) {\n            System.out.println(\"Released: \" + 100.0 * released / caught + \"%\");\n        }\n        if (kept > 0) {\n            System.out.println(\"Average length: \" + (double) total / kept + \" cm\");\n            System.out.println(\"Longest: \" + longest + \" cm\");\n        }\n    }\n}\n","hints":["Keep five counts before the loop: caught, released, kept, the total length of the kept fish, and the longest. Check for `0` right after reading, before anything is counted.","Every fish adds one to `caught`. A fish under 25 cm then adds one to `released`, and `continue` skips the rest. Check `caught > 0` and `kept > 0` before dividing by them."],"tests":[{"name":"Test 1","stdin":"34\n18\n52\n37\n22\n0\n","expect":"Fish caught: 5\nReleased: 40.0%\nAverage length: 41.0 cm\nLongest: 52 cm","hidden":false},{"name":"Test 2","stdin":"0\n","expect":"Fish caught: 0","hidden":true},{"name":"Test 3","stdin":"10\n24\n0\n","expect":"Fish caught: 2\nReleased: 100.0%","hidden":true},{"name":"Test 4","stdin":"25\n60\n30\n24\n0\n","expect":"Fish caught: 4\nReleased: 25.0%\nAverage length: 38.333333333333336 cm\nLongest: 60 cm","hidden":true}],"require":[],"forbid":[]},{"task":"A phone's screen-time log reads the minutes the screen was on each day, one number per line, until `end`. The rule: a day has 1440 minutes, so a value must be from 0 to 1440, and other lines are skipped (a skipped line isn't a day at all). The last line of the report is the longest run of days in a row with more than 2 hours (120 minutes). The program has two bugs. For the input shown, it prints `Days: 5`, `Average: 484.0 min`, `Most: 2000 min` and `Days in a row over 2 hours: 3`. Fix it, so that it prints:\n\n```\nDays: 4\nAverage: 105.25 min\nMost: 150 min\nDays in a row over 2 hours: 2\n```\n","kind":"code","seed":"import java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        int days = 0;\n        int total = 0;\n        int most = 0;\n        int run = 0;\n        int longestRun = 0;\n        while (true) {\n            String line = scanner.nextLine();\n            if (line.equals(\"end\")) {\n                break;\n            }\n            int minutes = Integer.valueOf(line);\n            if (minutes < 0 && minutes > 1440) {\n                continue;\n            }\n            days++;\n            total += minutes;\n            if (minutes > most) {\n                most = minutes;\n            }\n            if (minutes > 120) {\n                run++;\n                if (run > longestRun) {\n                    longestRun = run;\n                }\n            } else {\n                run = 0;\n            }\n        }\n        if (days == 0) {\n            System.out.println(\"No days logged\");\n        } else {\n            double average = total / days;\n            System.out.println(\"Days: \" + days);\n            System.out.println(\"Average: \" + average + \" min\");\n            System.out.println(\"Most: \" + most + \" min\");\n            System.out.println(\"Days in a row over 2 hours: \" + longestRun);\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(System.in);\n        int days = 0;\n        int total = 0;\n        int most = 0;\n        int run = 0;\n        int longestRun = 0;\n        while (true) {\n            String line = scanner.nextLine();\n            if (line.equals(\"end\")) {\n                break;\n            }\n            int minutes = Integer.valueOf(line);\n            if (minutes < 0 || minutes > 1440) {\n                continue;\n            }\n            days++;\n            total += minutes;\n            if (minutes > most) {\n                most = minutes;\n            }\n            if (minutes > 120) {\n                run++;\n                if (run > longestRun) {\n                    longestRun = run;\n                }\n            } else {\n                run = 0;\n            }\n        }\n        if (days == 0) {\n            System.out.println(\"No days logged\");\n        } else {\n            double average = (double) total / days;\n            System.out.println(\"Days: \" + days);\n            System.out.println(\"Average: \" + average + \" min\");\n            System.out.println(\"Most: \" + most + \" min\");\n            System.out.println(\"Days in a row over 2 hours: \" + longestRun);\n        }\n    }\n}\n","hints":["2000 was counted as a day, and it even made the run of long days one day longer. Can a number be below 0 **and** above 1440 at the same time? The rule's `if` must be true when either one holds.","`total / days` divides two `int` values, so the decimals are gone before the result is stored in the double. Cast first: `(double) total / days`."],"tests":[{"name":"Test 1","stdin":"95\n150\n2000\n131\n45\nend\n","expect":"Days: 4\nAverage: 105.25 min\nMost: 150 min\nDays in a row over 2 hours: 2","hidden":false},{"name":"Test 2","stdin":"end\n","expect":"No days logged","hidden":true},{"name":"Test 3","stdin":"1441\n-5\n0\nend\n","expect":"Days: 1\nAverage: 0.0 min\nMost: 0 min\nDays in a row over 2 hours: 0","hidden":true},{"name":"Test 4","stdin":"121\n120\n8\n1440\nend\n","expect":"Days: 4\nAverage: 422.25 min\nMost: 1440 min\nDays in a row over 2 hours: 1","hidden":true},{"name":"Test 5","stdin":"130\n140\n10\n150\n1500\n160\n170\nend\n","expect":"Days: 6\nAverage: 126.66666666666667 min\nMost: 170 min\nDays in a row over 2 hours: 3","hidden":true}],"require":[],"forbid":[]}]}