Records Exercises (22)

← Module home · ★ = first pass · ★★ = needs the toolkit · ★★★ = combines ideas

How to use this page. Attempt 15 minutes before opening any solution. The records habit: write the struct definition first, one sentence per field, before any function. ★ items need Lesson 1; ★★★ items combine all three lessons.


Part A — Struct basics (★, E1–E8)

E1. Define struct Point { int x; int y; };. Declare two Points — one with a brace list, one empty then filled by member assignment. Print both in the form (3, 4).

E2. The deep-copy proof: make Point a = {3, 4}; Point b = a; b.x = 99;. Print both. One sentence: what did = do, and what would the parallel-array equivalent have required?

E3. Define struct Rectangle { double width; double height; }; and write double area(Rectangle r) (by value — say why that’s fine here) and void scale(Rectangle& r, double k) (by reference — say why it must not be value). Test both.

E4. The ordering-rule demo: struct Pair { int a; int b; }; — Pair p = {5, 7};. Without running it, state what p.a and p.b are. Then swap the values in the brace list and state the new contents. What defence does the lesson prescribe for 5+ field records?

E5. Define Student (name, score) and a roster[3] initialized with a brace-list array-of-records. Print a table: Sr. | Name | Score with setw. Compute and print the average (cast rule!).

E6. The swap drill: write the one-move record swap of roster[0] and roster[2] from E5’s array, then write the parallel-array version as a comment block underneath — and count the lines.

E7. Read n (validated 1–50), then read n Student records into an array (name via getline + the mixing-trap ignore, score validated 0–100), then print the roster with the best scorer flagged with a *.

E8. Define struct Counter { int value; }; and explain — with a tiny main — why a struct wrapping one int still beats a bare int for readability when the int means something specific (e.g. a student count vs a timeout in seconds). This is the strongest form of the “organizing” idea: two structs with the same members are still different types.


Part B — Functions and nesting (★★, E9–E15)

E9. The three doors, one function each, all over struct Temp { double celsius; };: printByValue(Temp t), bumpByPointer(Temp* t, double by), normalizeByRef(Temp& t) (clamps into [−50, 50]). Write correct call lines for all three.

E10. Write Student readStudent() that builds and returns a record (validated), and void readInto(Student& s) that fills the caller’s record (same validation). Then one sentence: when would you prefer which?

E11. Define struct Date { int y; int m; int d; }; with bool isValid(const Date&) (year 1900–2100, month 1–12, day 1–31 — full month-length checking is the challenge’s job, not this one) and Date readDate() using the guard-chain loop. Test with 2024-13-01 (rejected) and 2024-02-29 (accepted — fine at this precision).

E12. Define Author (name, birthYear) and Book (title, writer, price — nested!). Build one book with a nested brace list, then by member assignment. Print a catalogue line: title (author, year) Rs. price.

E13. Write Book cheapest(const Book shelf[], int n) returning the lowest-price book (guard n ≤ 0 by returning a documented “empty” book — {"", {"", 0}, 0.0} — and say in a comment why a returned record can’t be null).

E14. Write void applyDiscount(Book& b, double pct) and explain why the parameter is not const& — then write void printBook(const Book& b) and explain why it is. Same type, opposite doors, one rule.

E15. The arrow drill: given Book* p = &shelf[1]; write both spellings of printing the title ((*p).title and p->title), then write the loop that walks the whole shelf with only a pointer (no [i]) printing titles — the Unit 13 traversal over records.


Part C — Enums and design (★★–★★★, E16–E22)

E16. Define enum class Level { BEGINNER, INTERMEDIATE, ADVANCED }; and a const char* levelText(Level) bridge function. Print all three levels in a loop over an array of the three values.

E17. Define struct Course { std::string title; int credits; Level level; };, build three courses, and print a catalogue grouped by level — each group headed by its levelText.

E18. The input trap, demonstrated: given enum class Level from E16, show the compile error for Level l = 1;, the legal-but-wrong static_cast<Level>(7) — and what happens when a switch meets it — then write the honest bool parseLevel(int code, Level& out) with validation.

E19. Write the menu mapper: bool pickLevel(int menuChoice, Level& out) mapping 1–3 to the levels, and a loop that prints the menu, reads a choice, re-prompts on garbage, and prints the chosen level’s text. (The boundary stays int; the logic stays enum.)

E20. Design drill (on paper, then code): a LibraryBook needs title, author, ISBN, status (Available/Borrowed/Reserved), borrower name (when borrowed), and due date. List the nouns, mark the closed set, name the nested record, then write the full schema with one-sentence field comments.

E21. Given this existing (bad) interface — void report(std::string titles[], double prices[], int counts[], int n) — rewrite it around a Product record. List exactly what improved (function signature, swap safety, self-documentation). Then state what didn’t change: the array-is-a-handle fact and the count’s travels.

E22. Capstone drill: combine everything — struct Patient { int id; std::string name; Date admitted; bool critical; }; with: readPatient (validating factory), printPatient (const&), countCritical(const Patient[], int), and a main that manages 3 patients and prints a ward report. (This is Lab 5’s seed — do it small here.)


Solutions (S1–S22)

S1 — Two Points

struct Point { int x; int y; };
Point a = {3, 4};
Point b;
b.x = 10; b.y = 20;
std::cout << '(' << a.x << ", " << a.y << ")\n";   // (3, 4)

Brace list for known-at-birth values; member assignment for filled-later ones.

S2 — Deep-copy proof

a prints (3, 4), b prints (99, 4). = copied both members — a deep field-wise copy. The parallel-array equivalent needed two moves (xa = xb; ya = yb;) — the drift surface E2 exists to expose.

S3 — area and scale

double area(Rectangle r) { return r.width * r.height; }   // small type, read-only: value is fine
void scale(Rectangle& r, double k) { r.width *= k; r.height *= k; }   // must modify the CALLER's

area by value: a 16-byte copy is cheaper than any ceremony, and the callee can’t corrupt the caller’s. scale by value would scale a copy — the caller’s rectangle would never change, silently.

S4 — The ordering rule

{5, 7} binds positionally: a=5, b=7. Swapped list: a=7, b=5 — legal, silent, wrong. Defence for 5+ fields: member assignment (Way 3), where each value sits beside its name; brace lists stay for short, adjacent-to-definition cases.

S5 — Roster table

Student roster[3] = { {"Ayesha Khan", 92}, {"Ali Raza", 78}, {"Sana Mir", 85}};
int total = 0;
for (int i = 0; i < 3; i = i + 1) {
    std::cout << std::setw(3) << i + 1 << " | "
              << std::setw(15) << std::left << roster[i].name << " | "
              << std::setw(3) << std::right << roster[i].score << '\n';
    total += roster[i].score;
}
std::cout << "average: " << (1.0 * total / 3) << '\n';

S6 — The swap drill

Student t = roster[0]; roster[0] = roster[2]; roster[2] = t;   // one move
/* parallel version:
std::string tN = names[0]; names[0] = names[2]; names[2] = tN;
int    tS = scores[0]; scores[0] = scores[2]; scores[2] = tS;   // two moves — twice the drift surface */

S7 — Validated roster input

int n;
std::cin >> n;
std::cin.ignore(1000, '\n');                 // before the getline loop
if (n < 1 || n > 50) return 0;
Student roster[50];
for (int i = 0; i < n; i = i + 1) {
    std::cout << "name: ";
    std::getline(std::cin, roster[i].name);
    do { std::cout << "score: "; std::cin >> roster[i].score;
         if (roster[i].score < 0 || roster[i].score > 100) std::cout << "0-100 please\n"; }
    while (roster[i].score < 0 || roster[i].score > 100);
    std::cin.ignore(1000, '\n');             // after every >> that precedes a getline
}
int best = 0;
for (int i = 1; i < n; i = i + 1) if (roster[i].score > roster[best].score) best = i;
for (int i = 0; i < n; i = i + 1)
    std::cout << roster[i].name << (i == best ? " *" : "") << '\n';

S8 — The one-member struct

struct StudentCount { int value; };
struct TimeoutSecs  { int value; };
void enroll(StudentCount n);        // enroll(timeout_in_seconds) does not compile!

Two types with identical members are still different types — the struct makes the unit of meaning part of the type system. A bare int accepts anything; a named record documents and enforces. (This is the seed of strong typing — and of Unit 15.)

S9 — Three doors, one type

void printByValue(Temp t)            { std::cout << t.celsius << '\n'; }
void bumpByPointer(Temp* t, double by) { if (t) t->celsius += by; }
void normalizeByRef(Temp& t) {
    if (t.celsius < -50) t.celsius = -50;
    if (t.celsius >  50) t.celsius =  50;
}
// calls:
printByValue(lab);          // a copy prints
bumpByPointer(&lab, 2.5);   // the address goes in; the target changes
normalizeByRef(lab);        // the alias modifies directly

S10 — readStudent vs readInto

Student readStudent() { Student s; /* read into s, validate */ return s; }
void    readInto(Student& s) { /* read into s, validate */ }

Return the record when the caller wants a new value (Student a = readStudent();); use the reference when filling an existing record (readInto(roster[count]);) — no extra copy, and the intent (“fill this one”) is visible at the call.

S11 — Date with rules

bool isValid(const Date& d) {
    return d.year >= 1900 && d.year <= 2100
        && d.month >= 1 && d.month <= 12
        && d.day   >= 1 && d.day   <= 31;
}
Date readDate() {
    Date d;
    do {
        std::cout << "yyyy mm dd: ";
        std::cin >> d.year >> d.month >> d.day;
        if (!isValid(d)) std::cout << "invalid - try again\n";
    } while (!isValid(d));
    return d;
}

2024-13-01 → rejected (month); 2024-02-29 → accepted (day ≤ 31 — the coarse check; month-length precision is the challenge tier).

S12 — Nested Book

Book b = {"The Little Prince", {"Antoine de Saint-Exupéry", 1900}, 9.99};
std::cout << b.title << " (" << b.writer.name << ", " << b.writer.birthYear
          << ") Rs. " << b.price << '\n';

The nested brace binds positionally: title, then the inner pair, then price. Reading descends: b.writer.name = b’s writer, the writer’s name.

S13 — cheapest

// No null possible for a returned record: a record IS a value — the "empty" book
// is the documented nothing (check its empty title at the call site if needed).
Book cheapest(const Book shelf[], int n) {
    int best = 0;
    for (int i = 1; i < n; i = i + 1)
        if (shelf[i].price < shelf[best].price) best = i;
    return shelf[best];
}

S14 — The door rule, twice

applyDiscount(Book& b, ...) — it must modify the caller’s record, so a reference; const& would forbid the write. printBook(const Book& b) — read-only and potentially large, so no copy + compiler-enforced protection. One rule, two doors: modify → &; read-only → const&.

S15 — The arrow drill

std::cout << (*p).title << '\n';   // explicit: follow, then dot
std::cout << p->title << '\n';     // the humane spelling

for (const Book* q = shelf; q != shelf + n; q = q + 1)
    std::cout << q->title << '\n';

The pointer walk composes both lessons: arithmetic strides by sizeof(Book), and -> reads each record’s field.

S16 — Level bridge

enum class Level { BEGINNER, INTERMEDIATE, ADVANCED };
const char* levelText(Level l) {
    switch (l) {
        case Level::BEGINNER:     return "beginner";
        case Level::INTERMEDIATE: return "intermediate";
        case Level::ADVANCED:     return "advanced";
    }
    return "?";
}
Level all[] = {Level::BEGINNER, Level::INTERMEDIATE, Level::ADVANCED};
for (int i = 0; i < 3; i = i + 1) std::cout << levelText(all[i]) << '\n';

S17 — Grouped catalogue

Course courses[3] = {
    {"Intro to Programming", 3, Level::BEGINNER},
    {"Data Structures",      4, Level::ADVANCED},
    {"OOP Fundamentals",     3, Level::INTERMEDIATE},
};
// group by level: iterate the enum values as the OUTER loop
Level order[] = {Level::BEGINNER, Level::INTERMEDIATE, Level::ADVANCED};
for (int g = 0; g < 3; g = g + 1) {
    std::cout << "-- " << levelText(order[g]) << " --\n";
    for (int i = 0; i < 3; i = i + 1)
        if (courses[i].level == order[g])
            std::cout << "  " << courses[i].title << " (" << courses[i].credits << " cr)\n";
}

The grouping trick: the enum drives the outer loop — closed sets enumerate naturally.

S18 — The input trap, fully

Level l = 1; — compile error (no int→enum-class conversion). static_cast<Level>(7) — compiles, produces a value no member has; a switch over it matches no case and (without a default) falls straight through, silently doing nothing. Honest parse:

bool parseLevel(int code, Level& out) {
    if (code < 0 || code > 2) return false;      // the closed set, enforced
    out = static_cast<Level>(code);
    return true;
}

S19 — Menu mapper

bool pickLevel(int menuChoice, Level& out) {
    switch (menuChoice) {
        case 1: out = Level::BEGINNER;     return true;
        case 2: out = Level::INTERMEDIATE; return true;
        case 3: out = Level::ADVANCED;     return true;
        default: return false;
    }
}
// loop: print menu; read int; if (!pickLevel(ch, lvl)) re-prompt; else print levelText(lvl)

The boundary stays int (input is int-shaped), the logic stays enum (state is name-shaped), and one mapping function owns the translation.

S20 — LibraryBook schema

enum class BookStatus { AVAILABLE, BORROWED, RESERVED };

struct LibraryBook {
    std::string title;        // the book's title
    std::string author;       // author's full name
    std::string isbn;         // ISBN-13, kept as text (leading zeros!)
    BookStatus  status;       // the closed set — the circulation state
    std::string borrower;     // "" when not borrowed (the sentinel)
    Date        due;          // when BORROWED; ignored otherwise
};

Nouns → fields; the one closed set (status) became the enum; the due date is the nested record; the borrower’s emptiness is the documented “not borrowed” signal. ISBN as text is a real-world lesson: it can start with 0 and contains dashes.

S21 — The interface rewrite

struct Product { std::string title; double price; int count; };
void report(const Product items[], int n);

Improved: one parameter instead of three-arrays-plus-count; swaps/moves of a product are one assignment; the type documents itself. Unchanged: the array still crosses as a handle (no copy), and the count still travels separately — the size convention survives every abstraction level.

S22 — Patient capstone drill

struct Patient { int id; std::string name; Date admitted; bool critical; };

Patient readPatient() {
    Patient p;
    std::cout << "id: ";            std::cin >> p.id;
    std::cin.ignore(1000, '\n');
    std::cout << "name: ";          std::getline(std::cin, p.name);
    std::cout << "admitted:\n";     p.admitted = readDate();
    std::cout << "critical (1/0): "; std::cin >> p.critical;
    std::cin.ignore(1000, '\n');
    return p;
}

void printPatient(const Patient& p) {
    std::cout << "#" << p.id << ' ' << p.name
              << " (admitted " << p.admitted.y << '-'
              << p.admitted.m << '-' << p.admitted.d << ')'
              << (p.critical ? " [CRITICAL]" : "") << '\n';
}

int countCritical(const Patient ward[], int n) {
    int c = 0;
    for (int i = 0; i < n; i = i + 1) if (ward[i].critical) c += 1;
    return c;
}

The seed of Lab 5: a validating factory, a const& printer, a counting pass — the whole procedural toolkit around one designed record.