Mini-Project — The Number Analysis Toolkit

← Module home · Unit 05–06 capstone · Everything from this module, one program

You’ve built ten labs’ worth of parts. This project asks you to design a program out of them — a menu-driven statistics and number-theory toolkit that runs until the user quits. No new syntax is introduced: if you need something you haven’t met (functions, arrays, strings-as-collections), you either copy-paste working blocks (as Lab 5 did) or descope honestly.

What you’re building

A console program that maintains, in memory, the statistics of a session of numbers entered one at a time, plus a set of single-number analysis tools. Menu:

==== Number Analysis Toolkit ====
1. Add a number to the session
2. Session statistics (count, total, average, min, max)
3. Times table of any n
4. Analyze a number (digits, palindrome, prime)
5. Pattern printing (triangle, pyramid)
6. Collatz journey of a number
0. Quit

Behaviour spec

Global rules. Non-numeric input anywhere re-prompts; out-of-range re-prompts with a message. The program never crashes and never gives up.

Why this project is the right capstone

Menu option Stage-B skills composed
Menu itself do-while + switch + sentinel quit (Lesson 3 §5)
Add validation loops (gallery) + streaming accumulator (no re-reads!)
Stats accumulator, counter, both champions (Lesson 2 §2)
Table / Patterns nested loops (Lesson 4)
Analyze digit peeling, prime test, divisor count
Collatz input-controlled loop with derived counter

The honest limitation to articulate in your write-up: “Add many numbers, then list them” is impossible here — the toolkit keeps only running statistics, never the numbers themselves. You will feel this wall; naming it precisely is the arrays module’s motivation. Similarly, every “robust reader” block you copy-paste is Stage C’s functions begging to exist — and the Utility Toolkit mini-project is your chance to rebuild this program the structured way.

Milestones

Build in this order; each milestone is a working program.

Your test plan (deliverable)

Write the test table before M4. Minimum rows: one per option, one boundary per range (n=1, n=12, n=13 for table; h=2, h=9, h=10 for patterns), one non-numeric per input site, stats with 0/1/3 numbers, Collatz 1 and 6, analyze 1 (prime guard), 2 (prime), 1221 (palindrome, 8 divisors), quit summary with 0 adds and with 3 adds. A project isn’t done when it works — it’s done when the table is green.

Write-up (deliverable)

Alongside the code, half a page each:

  1. Design decisions — loop choice per construct, justified with the §6 procedure.
  2. The walls — what you couldn’t do without functions/arrays (list the copy-pasted blocks; count the duplication).
  3. Idiom inventory — which of the unit’s patterns appear, and where.
  4. What you’d add next — two features and which missing tool each needs.

Reference solution

Attempt M1–M3 before reading. This is one sound shape — your structure may differ and be equally correct. It follows the milestones exactly.

// natoolkit.cpp — Number Analysis Toolkit (mini-project, Units 05-06)
// Build:  g++ -std=c++17 -Wall -Wextra natoolkit.cpp -o natoolkit
// LAR scope: iostream/iomanip only, no functions, no arrays — the
// copy-pasted reader blocks and the inability to list added numbers
// are DELIBERATE (see the write-up questions).

#include <iostream>
#include <iomanip>

constexpr int ADD_MIN = -1000, ADD_MAX = 1000;
constexpr int TABLE_MAX = 12;
constexpr int ANALYZE_MAX = 99999;
constexpr int HEIGHT_MIN = 2, HEIGHT_MAX = 9;

int main() {
    // session state (M2)
    int   count = 0;
    double total = 0;
    bool  haveFirst = false;      // champion seeding across the whole session
    double minV = 0, maxV = 0;
    int   picks = 0;

    int choice;
    do {
        std::cout << "\n==== Number Analysis Toolkit ====\n"
                  << "1. Add a number to the session\n"
                  << "2. Session statistics\n"
                  << "3. Times table of any n\n"
                  << "4. Analyze a number\n"
                  << "5. Pattern printing\n"
                  << "6. Collatz journey\n"
                  << "0. Quit\nChoice: ";
        std::cin >> choice;
        if (std::cin.fail()) {                 // menu is numeric too (M3)
            std::cin.clear();
            std::cin.ignore(1000, '\n');
            std::cout << "Enter the number of an option.\n";
            continue;                          // back to the menu
        }

        switch (choice) {
        case 1: {
            double x;
            bool ok = false;
            do {                               // gallery D + A composed
                std::cout << "Number (-1000..1000): ";
                std::cin >> x;
                if (std::cin.fail()) {
                    std::cin.clear();
                    std::cin.ignore(1000, '\n');
                    std::cout << "Numbers only, please.\n";
                } else if (x < ADD_MIN || x > ADD_MAX) {
                    std::cout << "Between -1000 and 1000, please.\n";
                } else {
                    ok = true;
                }
            } while (!ok);
            count += 1;
            total += x;
            if (!haveFirst || x < minV) minV = x;   // first value seeds champions
            if (!haveFirst || x > maxV) maxV = x;
            haveFirst = true;
            std::cout << "Added. " << count << " number(s) in session.\n";
            break;
        }
        case 2:
            if (count == 0) {
                std::cout << "No numbers yet.\n";
            } else {
                std::cout << std::fixed << std::setprecision(2)
                          << "Count: " << count
                          << "  total: " << total
                          << "  average: " << total / count << '\n'
                          << "Min: " << minV << "  Max: " << maxV << '\n';
            }
            break;
        case 3: {
            int n = 0;                          // inline reader: range 1..TABLE_MAX
            bool ok = false;
            do {
                std::cout << "n (1-" << TABLE_MAX << "): ";
                std::cin >> n;
                if (std::cin.fail()) {
                    std::cin.clear(); std::cin.ignore(1000, '\n');
                    std::cout << "Numbers only, please.\n";
                } else if (n < 1 || n > TABLE_MAX) {
                    std::cout << "Between 1 and " << TABLE_MAX << ", please.\n";
                } else {
                    ok = true;
                }
            } while (!ok);
            std::cout << "---- " << n << " times table ----\n";
            for (int i = 1; i <= 10; i = i + 1) {
                std::cout << std::setw(3) << i << " x " << std::setw(3) << n
                          << " = " << std::setw(5) << i * n << '\n';
            }
            break;
        }
        case 4: {
            long long n = 0;                   // long long: headroom for rev/dsum
            bool ok = false;
            do {                               // inline reader: 1..ANALYZE_MAX
                std::cout << "n (1-" << ANALYZE_MAX << "): ";
                std::cin >> n;
                if (std::cin.fail()) {
                    std::cin.clear(); std::cin.ignore(1000, '\n');
                    std::cout << "Numbers only, please.\n";
                } else if (n < 1 || n > ANALYZE_MAX) {
                    std::cout << "Between 1 and " << ANALYZE_MAX << ", please.\n";
                } else {
                    ok = true;
                }
            } while (!ok);
            long long orig = n;                // peeling consumes n
            int digits = 0;
            long long dsum = 0, rev = 0;
            while (n > 0) {
                dsum += n % 10;
                rev = rev * 10 + n % 10;
                digits += 1;
                n /= 10;
            }
            long long divisors = 0, dsumAll = 0;
            for (long long d = 1; d <= orig; d = d + 1) {
                if (orig % d == 0) { divisors += 1; dsumAll += d; }
            }
            std::cout << "Digits: " << digits << "  digit sum: " << dsum
                      << "  reversed: " << rev
                      << "  palindrome: " << (rev == orig ? "yes" : "no") << '\n'
                      << "Prime: " << (orig > 1 && divisors == 2 ? "yes" : "no")
                      << "  divisors: " << divisors
                      << "  divisor sum: " << dsumAll << '\n';
            break;
        }
        case 5: {
            int h = 0;                         // inline reader: HEIGHT_MIN..MAX
            bool ok = false;
            do {
                std::cout << "Height (" << HEIGHT_MIN << "-" << HEIGHT_MAX << "): ";
                std::cin >> h;
                if (std::cin.fail()) {
                    std::cin.clear(); std::cin.ignore(1000, '\n');
                    std::cout << "Numbers only, please.\n";
                } else if (h < HEIGHT_MIN || h > HEIGHT_MAX) {
                    std::cout << "Between " << HEIGHT_MIN << " and " << HEIGHT_MAX << ", please.\n";
                } else {
                    ok = true;
                }
            } while (!ok);
            for (int r = 1; r <= h; r = r + 1) {          // left triangle
                for (int c = 1; c <= r; c = c + 1) std::cout << "* ";
                std::cout << '\n';
            }
            std::cout << '\n';
            for (int r = 1; r <= h; r = r + 1) {          // pyramid
                for (int s = 1; s <= h - r; s = s + 1) std::cout << ' ';
                for (int c = 1; c <= 2 * r - 1; c = c + 1) std::cout << "*";
                std::cout << '\n';
            }
            break;
        }
        case 6: {
            int n = 0;                         // inline reader: n >= 1
            bool ok = false;
            do {
                std::cout << "n (>= 1): ";
                std::cin >> n;
                if (std::cin.fail()) {
                    std::cin.clear(); std::cin.ignore(1000, '\n');
                    std::cout << "Numbers only, please.\n";
                } else if (n < 1) {
                    std::cout << "1 or more, please.\n";
                } else {
                    ok = true;
                }
            } while (!ok);
            int steps = 0;
            std::cout << "Journey: " << n;
            while (n != 1) {
                if (n % 2 == 0) n = n / 2; else n = 3 * n + 1;
                steps += 1;
                std::cout << " -> " << n;
            }
            std::cout << "\nSteps: " << steps << '\n';
            break;
        }
        case 0:
            break;
        default:
            std::cout << "Unknown option.\n";
        }
        if (choice >= 1 && choice <= 6) picks += 1;
    } while (choice != 0);

    std::cout << "\nSession summary: " << count << " number(s)";
    if (count > 0)
        std::cout << ", average " << std::fixed << std::setprecision(2) << total / count;
    std::cout << ", " << picks << " menu pick(s). Bye!\n";
    return 0;
}

The reader blocks (M3/M4). The reference deliberately shows the honest cost of working without functions: each case carries its own validated reader, and four of them are near-identical — prompt, read, fail-check, range-check, repeat. That duplication is the lesson: when functions arrive, all four collapse into one readInRange(prompt, min, max) and about eighty lines evaporate. Count the duplication in your write-up.

Two subtleties worth noticing in the reference: