Chapter 03 · Reps

Functions and Collections — Reps

← Back to Chapter 3

Chapter 3 — Reps

Conditioning, not grading. Nobody sees these. Do all sixteen in order before you open Project 3.

Ground rules unchanged. Type it, compile it, run it. No pasting, no autocomplete, AI off — the point is the movement, not the answer. Compile every one with -Wall -Wextra and fix warnings before you look at output.

Every rep below ships with the exact output the finished program prints. That is your grader. If your output matches character for character, you are done; if it differs by one line, one space, or one digit, you have something to find. Reps 1–5 go with work session 1, Reps 6–10 with session 2, Reps 11–16 with session 3.


Reps 1–5: Functions — Name, Signature, Body (§3.1–§3.8)

Rep 1 — square

Write int square(int x) that returns x * x. In main, loop i from 1 to 5 and print one line per call, in the form square(1) = 1.

Expected output:

square(1) = 1
square(2) = 4
square(3) = 9
square(4) = 16
square(5) = 25

Rep 2 — is_even

Write bool is_even(int n) (hint: n % 2 == 0). In main, turn on cout << boolalpha; and loop from 1 to 10, printing three space-separated columns: the number, the word even or odd, and the bool itself.

Expected output:

1 odd false
2 even true
3 odd false
4 even true
5 odd false
6 even true
7 odd false
8 even true
9 odd false
10 even true

If you see 1 and 0 in the third column, you forgot boolalpha (§3.2).


Rep 3 — max_of and min_of

This is the one rep in the group that reaches forward — it needs const int arr[], int size from §3.14. Read §3.10 and §3.14 first (about ten minutes), then come back. It is here on purpose: the seam described in §3.9 is easier to feel than to read about.

Declare int values[6] = {12, 5, 30, 7, 19, 3};. Write int max_of(const int arr[], int size) and int min_of(const int arr[], int size). Seed the running best with arr[0], loop from index 1, and return it. Print the array on one line, then both results.

Expected output:

values: 12 5 30 7 19 3
max_of(values, 6) = 30
min_of(values, 6) = 3

Getting 12 back from either function means you seeded with arr[0] and then never compared it against the rest.


Rep 4 — The swap that does nothing

Four functions, one lesson (§3.5). Write void try_to_double(int x) and void actually_double(int& x), both doing x = x * 2. Then write void broken_swap(int a, int b) — a correct three-line swap with no & that prints a and b from inside itself before returning — and void swap_ints(int& a, int& b), the same three lines with &.

In main, start with int n = 5; and int x = 1, y = 99;, and print the values before and after each call.

Expected output:

start:                    n = 5
after try_to_double(n):   n = 5
after actually_double(n): n = 10
start:                    x = 1, y = 99
  inside broken_swap: a = 99, b = 1
after broken_swap(x, y):  x = 1, y = 99
after swap_ints(x, y):    x = 99, y = 1

Read line 5 against line 6 until it stops being surprising. broken_swap really did swap — its own two copies. That is Bug 5 in §3.20, it produces no error and no warning, and the fix is one character.


Rep 5 — Prototypes, guards, composition

Build the mini toolkit of §3.8, organized the §3.3 way: main at the top, prototypes above it, definitions below. Five functions:

  • bool is_valid_probability(double p)true when p is in [0, 1].
  • double bayes_update(double prior, double L_H, double L_notH) — the formula from §3.6.
  • double validated_bayes(...) — same three parameters, with guard clauses returning the sentinel -1.0 if any argument is not a valid probability, otherwise calling bayes_update. Do not retype the formula.
  • double chained_bayes(double prior, double L1_H, double L1_notH, double L2_H, double L2_notH) — calls bayes_update twice, no arithmetic of its own.
  • void print_result(string label, double value) — prints label: value.

In main, use prior = 0.5, L_H = 0.9, L_notH = 0.1. Then call validated_bayes(1.5, 0.9, 0.1) and validated_bayes(0.01, 0.99, 0.01), and check the sentinel before printing — a -1.0 handed straight to cout has helped nobody (§3.7).

Expected output:

Prior: 0.5
Posterior: 0.9
Posterior after a second identical piece of evidence: 0.987805
validated_bayes(1.5, 0.9, 0.1) rejected: probabilities must be in [0, 1].
validated_bayes(0.01, 0.99, 0.01): 0.5

That last line is the rare-condition result from §3.6, and it is worth two minutes of staring: evidence that is right 99% of the time, applied to a 1% hypothesis, gets you to a coin flip and no further.


Reps 6–10: Arrays — One Name, Many Values (§3.10–§3.14)

Rep 6 — Declare, initialize, traverse

Declare const int SIZE = 5; and int centuries[SIZE] = {4, 2, 5, 3, 4}; — the centuries of the five starter manuscripts. Print every element as centuries[0] = 4. Then accumulate a sum in a second loop and print the sum and the average, remembering static_cast<double> (§3.11). Finally declare int partial[SIZE] = {4, 2}; and int inferred[] = {4, 2, 5, 3, 4}; and print both on one line each.

Expected output:

All centuries:
  centuries[0] = 4
  centuries[1] = 2
  centuries[2] = 5
  centuries[3] = 3
  centuries[4] = 4
Sum:     18
Average: 3.6
partial: 4 2 0 0 0
inferred: 4 2 5 3 4

If Average prints 3, you divided two ints. If your lines end in a stray space, print " " << arr[i] instead of arr[i] << " ".


Rep 7 — Bug drill: off-by-one, both directions

Break Rep 6 on purpose, one change at a time, and run it each time (§3.12). Keep only the first loop.

Step 1 — correct. for (int i = 0; i < SIZE; i++). Expected output:

centuries[0] = 4
centuries[1] = 2
centuries[2] = 5
centuries[3] = 3
centuries[4] = 4

Step 2 — one too many. Change the condition to i <= SIZE. -Wall -Wextra says nothing at all — verify that yourself; it is the whole point. Expected output, on the machine this book was written on:

centuries[0] = 4
centuries[1] = 2
centuries[2] = 5
centuries[3] = 3
centuries[4] = 4
centuries[5] = 1

Your sixth line will almost certainly differ, because reading centuries[5] is undefined behavior (§3.12) and there is no fact of the matter about what is in that memory. The checkable criterion is not the number: it is that you got six lines instead of five, and the sixth value is not one of your five. Some machines print a huge number; some print 0; some crash with Segmentation fault. All four outcomes are the same bug.

Step 3 — one too few. Change the condition to i < SIZE - 1. This one is deterministic. Expected output:

centuries[0] = 4
centuries[1] = 2
centuries[2] = 5
centuries[3] = 3

Four lines. Your last manuscript vanished, silently, with no complaint from anybody. When a listing is missing its first or last item, check your bounds before you check anything else.


Rep 8 — Array functions, const, and a guard clause

Write four functions over int centuries[5] = {4, 2, 5, 3, 4}; (§3.14):

  • void print_array(const int arr[], int size) — prints [ 4 2 5 3 4 ].
  • int sum_array(const int arr[], int size).
  • double average_array(const int arr[], int size) — a guard clause returning 0.0 when size <= 0, then a call to sum_array with the static_cast. No loop of its own.
  • void zero_out(int arr[], int size) — note: no const, because it writes.

Call average_array(centuries, 0) to exercise the guard, then zero_out and print again to prove that a function really does modify the caller’s array.

Expected output:

centuries: [ 4 2 5 3 4 ]
sum:       18
average:   3.6
average of an empty array: 0
after zero_out(centuries, SIZE): [ 0 0 0 0 0 ]

That last line is the fact of §3.14 you cannot afford to forget: arrays are not copied. const on the read-only parameters is what stops that power from hurting you.


Rep 9 — Capacity vs. logical size

The pattern that carries you into Project 3 and all of Chapter 4 (§3.13). Declare a global const int MAX_ENTRIES = 6;, then in main a string entries[MAX_ENTRIES] and int count = 0. Write:

  • void print_entries(const string entries[], int count) — prints (empty) when count is 0, otherwise numbers the entries 1. through N. for the human reader while indexing from 0.
  • void add_entry(string entries[], int& count, string value) — a guard clause refusing to add when the array is full, then entries[count] = value; then count++, then a confirmation line. count must be int&.

Add the five starter manuscripts, list them, then add a sixth and a seventh so you see the capacity guard fire.

Expected output:

Empty database:
(empty)
Added P52. count = 1
Added Bodmer Papyri. count = 2
Added Codex Sinaiticus. count = 3
Added Codex Vaticanus. count = 4
Added Codex Bezae. count = 5
Current list:
  1. P52
  2. Bodmer Papyri
  3. Codex Sinaiticus
  4. Codex Vaticanus
  5. Codex Bezae
Added Codex Alexandrinus. count = 6
Database full - cannot add Codex Washingtonianus
Final count: 6

If your list starts with a blank line, you incremented before assigning (Bug 13). If count is still 0 in main, you dropped the &.


Rep 10 — Remove by shifting

Add to Rep 9 a case-sensitive int find_index(const string entries[], int count, string target) returning the index or -1, and void remove_entry(string entries[], int& count, string target) exactly as §3.13 builds it: guard clause on -1, then for (int j = at; j < count - 1; j++) entries[j] = entries[j + 1];, then count--.

Start from the same five, then remove one from the middle, one that isn’t there, the last one, and the first one — printing the list after each successful removal.

Expected output:

Start:
  1. P52
  2. Bodmer Papyri
  3. Codex Sinaiticus
  4. Codex Vaticanus
  5. Codex Bezae
Removed Codex Sinaiticus. count = 4
  1. P52
  2. Bodmer Papyri
  3. Codex Vaticanus
  4. Codex Bezae
Not found: Codex Ephraemi
Removed Codex Bezae. count = 3
  1. P52
  2. Bodmer Papyri
  3. Codex Vaticanus
Removed P52. count = 2
  1. Bodmer Papyri
  2. Codex Vaticanus

Write j < count instead of j < count - 1 and the final copy reads entries[count] — the out-of-bounds read from §3.12, on data you own, which is exactly why it will not crash and will not warn you.


Reps 11–16: Strings, Search, Sort, and the Whole Week (§3.15–§3.19)

Rep 11 — String operations

One program, string verse = "Isaiah 1:18"; (the epigraph of this chapter). Print, using boolalpha: the length; the first and last characters by index; the position of ":"; the text before and after the colon with substr; substr(0, 6); the result of find("Q") and string::npos side by side, and the != string::npos test. Then concatenate "Codex", " ", and "Sinaiticus" into one string; compare "Bodmer" and "Codex" with == and <; and write to_lower and same_ignore_case exactly as §3.15 gives them — size_t counter, static_cast<char>, parameter by value.

Expected output:

verse:            Isaiah 1:18
length:           11
first char:       I
last char:        8
position of ':':  8
before the colon: Isaiah 1
after the colon:  18
substr(0, 6):     Isaiah
find("Q"):        18446744073709551615
string::npos:     18446744073709551615
found "Q"?        false
concatenated:     Codex Sinaiticus
a == b:           false
a < b:            true
same_ignore_case("Codex", "CODEX"):      true
same_ignore_case("Sinaiticus", "Bodmer"): false
to_lower(full):    codex sinaiticus
full is untouched: Codex Sinaiticus

Two things to verify with your own eyes. The two twenty-digit numbers are identical — that is what npos is, and why you never test a find result against -1 (Bug 9). And the last two lines prove that to_lower changed only its own copy, because §3.5 was used on purpose.


Rep 12 — Linear search, exact and substring

Reuse to_lower. Over string roster[5] = {"P52", "Bodmer Papyri", "Codex Sinaiticus", "Codex Vaticanus", "Codex Bezae"}; write both flavors from §3.16, in the book’s parameter order — collection, size, target:

  • int find_index(const string roster[], int size, string target) — lowercases both sides, returns the index or -1.
  • int search_substring(const string roster[], int size, string query) — prints each match as it finds it and returns the count, using .find(q) != string::npos.

Look up "P52", "codex bezae", "Codex Ephraemi", and "codex"; then run the substring search for "codex" and "papyr". Call search_substring on its own line and print the count afterwards — never from inside the same cout chain (§3.19).

Expected output:

=== Exact lookup (case-insensitive) ===
"P52" -> index 0 (P52)
"codex bezae" -> index 4 (Codex Bezae)
"Codex Ephraemi" -> not found
"codex" -> not found
=== Substring search for "codex" ===
  Codex Sinaiticus
  Codex Vaticanus
  Codex Bezae
Matches: 3
=== Substring search for "papyr" ===
  Bodmer Papyri
Matches: 1

Line 5 against lines 6–10 is the distinction worth owning: "codex" matches nothing exactly, and appears inside three names. Project 3’s Normal tier wants the second behavior, because that is what a human expects a search box to do.


Rep 13 — Bug drill: six compiler messages, on purpose

You have no one to read your errors for you, so read six of them deliberately while nothing is at stake. Start from your finished Rep 8. Make one change, compile, read the message, undo it, move on. Expected output: six compiler messages, one per break, in this order. These are the exact lines GCC 14 printed here, and they match the messages catalogued in §3.20; clang words some of them differently, noted where it matters.

1. Move sum_array’s definition below main and add no prototype (§3.3, Bug 1).

error: 'sum_array' was not declared in this scope

Clang: error: use of undeclared identifier 'sum_array'. Fix it twice — once by moving the definition back above main, once by leaving it below and adding a prototype.

2. Call sum_array(centuries) with no size (Bug 3).

error: too few arguments to function 'int sum_array(const int*, int)'

Note the const int* — that is the compiler telling you what an array parameter really is, three chapters before Chapter 6 explains it. Clang: error: no matching function for call to 'sum_array'.

3. Call sum_array(centuries[], SIZE) (Bug 12).

error: expected primary-expression before ']' token

The brackets live in the parameter list of the definition, never in the call.

4. Inside sum_array, add arr[i] = arr[i] + 1; (Bug 12).

error: assignment of read-only location '*(arr + ((sizetype)(((long unsigned int)i) * 4)))'

Ugly, and it means one thing: you promised const and then wrote. The compiler is enforcing your own promise.

5. Wrap sum_array’s body in if (size > 0) { ... } and delete nothing else (Bug 2).

warning: control reaches end of non-void function [-Wreturn-type]

A warning, not an error — it compiles, it runs, and what comes back on the size <= 0 path is garbage. Clang: warning: non-void function does not return a value in all control paths.

6. In Rep 11’s to_lower, change size_t i to int i (Bug 7).

warning: comparison of integer expressions of different signedness: 'int' and
'std::__cxx11::basic_string<char>::size_type' {aka 'long unsigned int'} [-Wsign-compare]

Also a warning; the program still prints codex sinaiticus. Fix it with size_t, not by turning -Wextra off.

Wording varies a little between compiler versions. The meaning does not, and §3.20 decodes all six.


Rep 14 — Trace it on paper first: selection sort

Do not type anything yet. On paper, write out {7, 3, 9, 2, 8} and hand-run the selection sort of §3.17. For each of the four passes, write down two things: the index the smallest remaining value was found at, and the whole array after the swap. Take the four minutes. This is the single highest-yield drill in the chapter, because Part A of the midterm is code reading and this is what code reading feels like.

Now write the program: print_array, and void selection_sort(int arr[], int size) with the outer loop stopping at size - 1, the inner search from i + 1, and the three-line swap through temp. Print the pass line and the array from inside the outer loop, after the swap.

Expected output:

start:  [ 7 3 9 2 8 ]
  pass i=0: smallest was at index 3 -> [ 2 3 9 7 8 ]
  pass i=1: smallest was at index 1 -> [ 2 3 9 7 8 ]
  pass i=2: smallest was at index 3 -> [ 2 3 7 9 8 ]
  pass i=3: smallest was at index 4 -> [ 2 3 7 8 9 ]
sorted: [ 2 3 7 8 9 ]

Compare against your paper line by line. Pass i=1 is the one people get wrong on paper: the smallest remaining value was already in place, so the swap exchanged an element with itself and the array did not visibly change. Normal, not a bug. If your paper matched all four passes, your mental model of this algorithm is correct and you can stop worrying about it.


Rep 15 — Parallel arrays, and the drift bug

Three arrays in lockstep (§3.18): string names[MAX], int centuries[MAX], string types[MAX], plus int count. Write add_manuscript(...) taking all three arrays, int& count, and the three field values; print_table(...); and sort_by_century(...), a selection sort on centuries whose swap moves all three arrays — nine lines to move one row.

Enter the starter set in this order: Codex Sinaiticus (4, uncial), P52 (2, papyrus), Codex Bezae (5, uncial), Bodmer Papyri (3, papyrus), Codex Vaticanus (4, uncial).

Expected output:

=== As entered ===
  1. Codex Sinaiticus (century 4, uncial)
  2. P52 (century 2, papyrus)
  3. Codex Bezae (century 5, uncial)
  4. Bodmer Papyri (century 3, papyrus)
  5. Codex Vaticanus (century 4, uncial)
=== Sorted by century ===
  1. P52 (century 2, papyrus)
  2. Bodmer Papyri (century 3, papyrus)
  3. Codex Sinaiticus (century 4, uncial)
  4. Codex Vaticanus (century 4, uncial)
  5. Codex Bezae (century 5, uncial)

Now break it. Delete the three lines that swap names, leaving the century and type swaps alone. Run it again:

=== Sorted by century ===
  1. Codex Sinaiticus (century 2, papyrus)
  2. P52 (century 3, papyrus)
  3. Codex Bezae (century 4, uncial)
  4. Bodmer Papyri (century 4, uncial)
  5. Codex Vaticanus (century 5, uncial)

Look at it hard. No error. No crash. The centuries are still perfectly sorted, the types still line up with the centuries, every row still looks like a plausible database record — and not one row says what you entered. You typed Codex Sinaiticus as a 4th-century uncial and the report now calls it a 2nd-century papyrus. That is Bug 14 in §3.20 and it is the worst kind of bug there is: silent, plausible, and permanent.

One honest footnote. In this tiny program -Wextra happens to catch it, because deleting those three lines leaves names unused and you get warning: unused parameter 'names' [-Wunused-parameter]. In a real program names is still used somewhere else in the same function, the warning never appears, and nothing on earth tells you but your own eyes. Chapter 4’s structs delete this bug from existence in one move.


Rep 16 — The whole week in one file

Assemble everything, reusing the functions you have already written and tested. Same three parallel arrays, same five manuscripts, same entry order as Rep 15. Your main must contain no loops, no comparisons, and no arithmetic — just declarations and a script of calls (§3.19). In order: print the table as entered; look up "codex bezae" with the case-insensitive find_index from Rep 12 and report the index; run the substring search for "codex" and print the count after the call; print six statistics using min_of, max_of, average_array, and a new int count_type(const string types[], int count, string type); sort by century; print the table again.

Expected output:

=== Database as entered ===
  1. Codex Sinaiticus (century 4, uncial)
  2. P52 (century 2, papyrus)
  3. Codex Bezae (century 5, uncial)
  4. Bodmer Papyri (century 3, papyrus)
  5. Codex Vaticanus (century 4, uncial)
=== Exact lookup ===
  codex bezae -> index 2 (Codex Bezae)
=== Substring search for "codex" ===
  Codex Sinaiticus
  Codex Bezae
  Codex Vaticanus
Matches: 3
=== Statistics ===
  entries:          5
  earliest century: 2
  latest century:   5
  average century:  3.6
  papyri:           2
  uncials:          3
=== Sorted by century ===
  1. P52 (century 2, papyrus)
  2. Bodmer Papyri (century 3, papyrus)
  3. Codex Sinaiticus (century 4, uncial)
  4. Codex Vaticanus (century 4, uncial)
  5. Codex Bezae (century 5, uncial)

Three checks. The lookup found Codex Bezae at index 2, not 4 — index in entry order, before the sort. average century: 3.6, not 3, or your static_cast is missing. And after the sort, P52 still says century 2 and papyrus, which means your nine-line swap moved all three columns.

If this program runs clean, Project 3’s Manuscript Database is mostly assembly work, and you have already done the hard parts.


Done? One Last Thing.

Open a blank file, from_memory_3.cpp. Close this page. No scrolling back, no autocomplete, no AI. Write:

  1. int sum_array(const int arr[], int size).
  2. double average_array(const int arr[], int size) with a guard clause returning 0.0 when size <= 0, which calls sum_array — no second loop.
  3. int find_index(const string names[], int size, string target) returning the index or -1.
  4. A report helper that calls find_index and handles both outcomes.
  5. A main with int centuries[5] = {4, 2, 5, 3, 4}; and string names[5] = {"P52", "Bodmer Papyri", "Codex Sinaiticus", "Codex Vaticanus", "Codex Bezae"}; that prints the sum, the average, the average of an empty array, and a report for "Codex Bezae" and "Codex Ephraemi".

Expected output:

sum:     18
average: 3.6
average of an empty array: 0
"Codex Bezae" -> index 4
"Codex Ephraemi" -> not found

Compile with -Wall -Wextra. If it works the first time with zero warnings, you have the move this whole week was for: a named function that walks a collection, written cold from a blank file. Take the Checkpoint in §3.22 and go build the project.


Up next: Project 3 — Project 3: Reasoning Toolkit & Manuscript Database.