This example brings several earlier ideas into one program: read a value, decide whether it belongs in the collection, store accepted values, then calculate a mean and a median. Read it as a sequence of decisions rather than trying to understand the whole listing at once. First follow one small input by hand; afterward, run it and compare the result with that prediction.
The mean is the total divided by the number of values. The median comes from their sorted order. Those definitions require a nonempty collection, so checking for emptiness is part of the calculation rather than a separate cosmetic feature.
State the input policy #
The program stores at most six scores in the inclusive range 0 through 100. The value -1 is a sentinel: it tells the input loop to stop without storing -1 as a score. Reaching six scores also stops the loop, before it tries to read a seventh value.
If numeric extraction fails, the program checks whether the stream reports end-of-file. It treats that case as an ending; otherwise, it prints an input error and returns failure. A numeric value outside the allowed range causes a different error. This is the exact demonstration policy, not a complete input parser: a failed numeric read that also reaches end-of-file can follow the ending branch. Do not describe it as rejecting every possible malformed final token.
#include <algorithm>
#include <iomanip>
#include <iostream>
#include <vector>
double median(std::vector<double> values) {
// Precondition: values is nonempty.
std::sort(values.begin(), values.end());
const auto middle = values.size() / 2;
if (values.size() % 2 != 0) return values[middle];
return (values[middle - 1] + values[middle]) / 2.0;
}
int main() {
constexpr std::size_t limit = 6;
std::vector<double> scores;
std::cout << "Enter scores 0..100; -1 ends input.\n";
while (scores.size() < limit) {
double candidate{};
if (!(std::cin >> candidate)) {
if (std::cin.eof()) break;
std::cerr << "Invalid numeric input.\n";
return 1;
}
if (candidate == -1.0) break;
if (!(candidate >= 0.0 && candidate <= 100.0)) {
std::cerr << "Score outside 0..100.\n";
return 1;
}
scores.push_back(candidate);
}
if (scores.size() == limit) {
std::cout << "Capacity reached: six scores accepted.\n";
}
if (scores.empty()) {
std::cout << "No observations.\n";
return 0;
}
double total = 0.0;
for (double score : scores) total += score;
std::cout << std::fixed << std::setprecision(2)
<< "Mean: " << total / scores.size() << '\n'
<< "Median: " << median(scores) << '\n';
}Build the collection before calculating #
The headers provide sorting, output formatting, streams, and vectors. The function definition above main tells C++ how to calculate a median, but execution of this program begins in main. limit is the constant 6, and scores starts as an empty vector. Its size is initially zero.
The loop condition checks size against the limit before each read. Inside the body, candidate is a local double initialized to zero. Input attempts to replace that value. If extraction fails, execution handles the stream state and does not append this candidate. If input succeeds, the sentinel check comes next. A candidate of -1 stops the loop without being stored.
The next condition requires both candidate >= 0.0 and candidate <= 100.0. The surrounding negation selects the error branch when that combined requirement is false. Only a candidate that passes these decisions reaches scores.push_back(candidate). Appending increases size by one. The loop then returns to its size check.
For input 10 30 20 -1, the vector changes from empty to [10], then [10, 30], then [10, 30, 20]. The -1 stops input, leaving size 3. A vector may reserve more storage internally, but this program's policy limit is a separate count. Six accepted scores is its limit regardless of the vector's actual storage capacity.
After the loop, size equal to the limit causes a capacity message. This means the program reached its six-score policy limit; it does not report the vector's internal capacity. Any additional input remains unread. Next comes the empty check. If no scores were accepted, the program prints No observations. and returns before any division or median access.
Trace the mean and the copied median #
For the three-score example, total begins at 0.0. The range loop reads 10, 30, and 20 in that order. After each addition, total is 10, then 40, then 60. Dividing by the size gives 60 divided by 3, or 20. Because total is a double, this is floating-point division. std::fixed with std::setprecision(2) displays two digits after the decimal point, so the mean prints as 20.00. Formatting does not change the values stored in the vector.
The median function receives its vector parameter by value. That creates its own copy. It can sort this copy while the caller's scores remains [10, 30, 20]. std::sort uses the half-open range from begin() up to, but not including, end().[1] The end iterator marks the position after the last element; it is not an extra element to read.
The local copy becomes [10, 20, 30]. middle is size divided by 2 with integer arithmetic: 3 divided by 2 gives 1. The remainder test finds that 3 is odd, so the function returns the element at index 1, which is 20. The median output is 20.00.
For input 10 20 -1, the sorted copy has size 2 and middle 1. The even case uses indices middle - 1 and middle, which are 0 and 1. Their values sum to 30, and division by 2.0 gives 15.0. Both mean and median print as 15.00. The function's nonempty precondition matters: with size zero, the even-case indices would not identify valid elements. main satisfies this precondition by returning on empty input first.
Use checks that expose boundaries #
For -1, no values are stored, and no summary division occurs. For 101, successful numeric extraction is followed by range rejection. For hello supplied as ordinary input without immediate end-of-file, numeric extraction fails and the error branch is taken. For 0 0 0 0 0 100, six scores are accepted. The program prints the capacity message, then mean 16.67 and median 0.00. The median uses the two central zero values, even though one extreme value raises the mean.
Before changing the program, explain why each check has that result. Then try a boundary pair such as 0 and 100, a single score, duplicates, and input that ends without the sentinel. Distinguish a predicted numeric result from the policy that determines whether a result is produced at all.
Exercise: add minimum and maximum while preserving the stored order. After the empty check, initialize both from the first score, then visit the remaining scores. Replace the current minimum only on a smaller value and the maximum only on a larger one. Test one score: it must be both the minimum and maximum. Repeated equal scores should not require a special case.
Exercise: replace the vector with a built-in array of capacity six. Maintain a separate used count, check room before appending, and traverse only [0, used). The physical array may have six elements while only two are accepted observations. Copy only those active values before sorting for a median, so unused storage does not enter the calculation.
Readiness map for every CS2 lecture #
For CS2 Lectures 1 and 2, revisit functions, parameter effects, and scope in the sixth foundation article. Be able to distinguish a changed local copy from a changed object reached through a reference. For Lecture 3, use input checks and loop tracing from the third and fifth articles together with array bounds from the seventh. Explain which inputs make a test pass or fail.
For Lecture 4, use the seventh article's distinction between physical capacity and active count, and its array interfaces. For Lecture 5, combine searching and sorting from the eighth with coordinates and nested loops from the ninth. State what each pass or loop index establishes.
For Lectures 6 and 7, return to addresses, aliases, lifetime, pointer arithmetic, and ownership in the eleventh article. Draw separate boxes for pointer variables and their targets. For Lecture 8, combine its allocate-copy-release resize sequence with the ninth article's grid layouts. Explain why separate rows need separate cleanup.
For Lectures 9 and 10, use the tenth article's character storage, null terminators, value comparison, line input, replacement, and searches. Distinguish an address comparison from a content comparison. For Lecture 11, combine character-set searches from that article with size, capacity, bounds, and alias invalidation from the twelfth.
Readiness map for every CS2 lab #
For Lab 1, use compilation from the first article, conversions and percentages from the second, and repeated observations from the fifth. Separate changing output text from changing the calculation.
For Lab 2, use validation from the third article, active counts from the seventh, and median reasoning from the eighth. Check empty input before calculating a result. For Lab 3, combine menu decisions from the fourth, searches and record-preserving movement from the eighth, and structures from the twelfth.
For Lab 4, use the first, fourth, fifth, and eleventh articles to distinguish compiler and logic errors, trace variables, validate mathematical domains, and explain each pointer's lifetime. For Lab 5, use the ninth and eleventh articles to name dimensions, separate old and next simulation state, and trace nested allocation, resizing, and cleanup.
For a final review, choose a line and explain what it reads, what it changes, which range is valid, and which object owns any storage involved. If a step remains unclear, trace a smaller example from the relevant article. Continue with CS2: functions and parameters, CS2: pointers and allocation, and CS2: strings and vectors.