Chapter 18 — Iterators and Algorithms: VecTools
You are completing vectools, a small library that performs five common
operations on std::vector<int>. For each operation you implement two
versions that must agree on every input:
- A
hand*version that walks the vector with an explicit iterator loop — you write thebegin()/end()/++it/*itmachinery yourself so you see the abstraction in full. - An
algo*version that delegates to a standard<algorithm>(std::find,std::count_if,std::sort,std::max_element), passing a free-function predicate or comparator.
The grader confirms that both versions agree, so a mismatch in either direction is caught immediately. The "two ways, same result" structure is the lab's physical demonstration of Chapter 18's central lesson: the standard library algorithms are not magic — they run the same iterator loop you would have written by hand, just packaged with a name.
Why free functions, not lambdas? Lambdas are the natural next step as predicates, but they are introduced in Chapter 20. The Chapter 18 way is a plain free function whose address decays to a function pointer:
bool isEven(int n) { return n % 2 == 0; }
std::count_if(v.begin(), v.end(), vectools::isEven); // function pointerThis is also historically how all C++ algorithm predicates worked before C++11. Lambdas will make this more concise in Chapter 20; for now, the free-function form is what you use.
Your tasks
The starter compiles and runs immediately (make build is green), but every
function returns a wrong placeholder, so make test is RED. Fill in the
>>> YOUR CODE HERE <<< blocks in starter/vectools.cpp.
Each task label below maps 1:1 to a TASK block in the starter:
Task 1a — isEven(n) — return true iff n is divisible by 2. This is the
predicate passed by name to std::count_if in Task 3b. Negative numbers
count too: isEven(-4) == true.
Task 1b — greaterThan(a, b) — return true iff a > b. This is the
comparator passed by name to std::sort in Task 4b. Use strictly >
— not >=: equal elements must not claim to come before each other
(strict-weak-ordering rule; violating it is undefined behaviour in std::sort).
Task 2a — handFind(vec, target) — search vec for target using an
explicit iterator loop; return the 0-based index of the first match, or -1
if not found. Write the loop with auto it { vec.begin() }; … ++it; *it. Convert
the winning iterator to an index with std::distance(vec.begin(), it). Do NOT
call any <algorithm>.
Task 2b — algoFind(vec, target) — same contract as handFind; implement by
calling std::find(vec.begin(), vec.end(), target). The returned iterator must be
checked against vec.end() before dereferencing (notes 18.2).
Task 3a — handCountEven(vec) — count even elements using an explicit
iterator loop; test *it % 2 == 0; tally manually. Do NOT call any
<algorithm>.
Task 3b — algoCountEven(vec) — same contract; call
std::count_if(vec.begin(), vec.end(), vectools::isEven). Pass the free
function isEven (no lambdas — Chapter 20). Cast the std::ptrdiff_t return
to int.
Task 4a — sortAscending(vec) — sort the vector in ascending order using
std::sort(vec.begin(), vec.end()). The default comparator uses operator<.
Task 4b — sortDescending(vec) — sort descending by calling
std::sort(vec.begin(), vec.end(), vectools::greaterThan). Pass the free
function greaterThan as the comparator.
Task 5 — largest(vec) — return the largest element, or 0 for an empty
vector. Call std::max_element(vec.begin(), vec.end()). Guard the empty-range
trap: the returned iterator equals vec.end() when the vector is empty —
dereferencing it is undefined behaviour. Check first; return the 0 fallback.
You do not edit vectools.h or tests/tests.cpp — only starter/vectools.cpp.
Success criteria
isEvenandgreaterThanare tested directly before being used in algorithms.handFind/algoFindare checked against each other (they must agree on every input), and edge-tested: empty vector, single element, duplicates (first occurrence wins), target absent.handCountEven/algoCountEvenare cross-checked: all-even, all-odd, empty, single element, negative numbers (-4is even).sortAscending/sortDescendingare checked element-by-element: already-sorted input, all-same elements, single element, empty vector.largesthandles: normal case, single element, empty vector (must return0, not crash), all-same, all-negative, mixed signs.
Concepts practiced
- Iterator objects —
begin(),end(), dereference*it, advance++it, sentinel testit != end()(notes 18.2) - Half-open range
[begin, end)—begin()is valid,end()must not be dereferenced (notes 18.2) std::find— searches a range; returns an iterator orend()(notes 18.3)std::count_if— counts predicate matches using a free-function predicate (notes 18.3)std::sort— default ascending and custom descending via a free-function comparator; strict-weak-ordering rule (notes 18.3)std::max_element— returns an iterator; empty-range guard required.max_elementitself is not in the note; the iterator-return +end()-guard pattern it shares withstd::findis (notes 18.2)- Function pointers as predicates/comparators — free functions passed by name to algorithms (Chapter 18 scope; lambdas arrive Chapter 20)
std::distance— converts an iterator to an index (Chapter 18 / 17 pointer arithmetic)- Reused from earlier chapters:
std::vectorconstruction and.begin()/.end()(Ch 16),constreferences and pass-by-ref (Ch 12), namespaces and header/source split (Ch 2),forloops (Ch 8)
Constraints
Allowed: std::vector, iterator objects (.begin(), .end(), ++it, *it,
it != end), <algorithm> algorithms, std::distance, free-function predicates
and comparators, everything from Chapters ≤ 18.
Required idioms:
handFindandhandCountEvenmust use explicit iterator loops — no<algorithm>inside those two functions.algoCountEvenmust passvectools::isEvenas the predicate (a free function pointer).sortDescendingmust passvectools::greaterThanas the comparator.largestmust guard the empty-range case before dereferencingstd::max_element's return value.
Forbidden (not taught yet):
- Lambdas — Chapter 20. Use free functions (
isEven,greaterThan) instead. std::function— Chapter 20.- C++20 ranges (
std::ranges::sort, etc.) — out of scope here. new/delete— Chapter 19.
Build & run locally
make # compile-check starter/vectools.cpp (warning-clean)
make test # grade your code against the unit tests <-- the main goal
make solution # run the grader against the reference if you get stuck
make test-solution # verify the reference solution (must be green)
make clean # remove build artefactsHints
Tasks 1a & 1b — isEven and greaterThan
Both are one-liners:
bool isEven(int n) { return n % 2 == 0; }
bool greaterThan(int a, int b) { return a > b; }The greaterThan placeholder returns false for everything, so sortDescending
will leave the vector in its original order — a clear test failure.
Tasks 2a & 3a — the iterator loop (handFind, handCountEven)
The canonical iterator loop from notes 18.2:
for (auto it { vec.begin() }; it != vec.end(); ++it)
{
// *it is the current element
}For handFind, once *it == target, convert to an index:
return static_cast<int>(std::distance(vec.begin(), it));std::distance counts the steps from begin to it — for a std::vector
iterator this is O(1) because vector iterators support random access.
Task 2b — checking the std::find iterator before using it (algoFind)
auto it { std::find(vec.begin(), vec.end(), target) };
if (it == vec.end())
return -1; // not found — do NOT dereference here
return static_cast<int>(std::distance(vec.begin(), it));Always check it != vec.end() before writing *it. Dereferencing end() is
undefined behaviour — the iterator points one past the last element, not at any
real value (notes 18.2).
Task 3b — passing a free function to std::count_if
int algoCountEven(const std::vector<int>& vec)
{
return static_cast<int>(
std::count_if(vec.begin(), vec.end(), vectools::isEven));
}vectools::isEven decays to a bool(*)(int) function pointer. That is the
Chapter 18 predicate form — lambdas ([](int n){ return n%2==0; }) do the same
thing but aren't introduced until Chapter 20.
Task 1b — strict-weak-ordering in the comparator (greaterThan)
std::sort requires the comparator to be a strict weak ordering: comp(x, x)
must always return false (irreflexivity). Using >= breaks this: when a == b,
a >= b is true, meaning a claims to come before b and b claims to come
before a simultaneously. That contradiction gives std::sort undefined behaviour.
Use strictly >:
bool greaterThan(int a, int b) { return a > b; }When a == b, a > b is false — neither claims priority. Correct.
Task 5 — the empty-range guard for std::max_element (largest)
int largest(const std::vector<int>& vec)
{
auto it { std::max_element(vec.begin(), vec.end()) };
if (it == vec.end())
return 0; // empty range — fallback; do NOT dereference
return *it;
}When vec is empty, vec.begin() == vec.end() and std::max_element returns
vec.end() immediately. The placeholder return 0 is accidentally correct for
the empty case but wrong for everything else — the grader has non-empty cases that
will still fail.
Stretch goals
- Add
handMinandalgoMin(usingstd::min_element) and test them the same way. Reinforce the empty-range guard. - Add
algoContains(vec, target)returningbool, implemented withstd::find. Once you have it, observe thathandFind(v, t) != -1is equivalent — exactly what the standard library'sstd::ranges::contains(C++23) does under the hood. - Add
handFindIfandalgoFindIfusingstd::find_ifwith a free-function predicate of your choice. This previewsstd::find_iffrom notes 18.3. - Add a
printAll(vec)that uses a range-basedforloop (Ch 16) — then rewrite it asstd::for_eachwith a free function, and compare readability. Notes 18.3 addresses whenstd::for_eachadds value vs. when a plain loop is clearer. - Once you reach Chapter 20 (lambdas), rewrite
algoCountEvenandsortDescendingto use inline lambda predicates instead of free functions. Notice that the call sites become single self-contained expressions — that is the payoff lambdas deliver.
// Chapter 18 — Iterators and Algorithms · Project: VecTools (STARTER)
// ─────────────────────────────────────────────────────────────────────────────
// Fill in the TASK blocks below. Each maps 1:1 to a task in the README and to a
// declaration in ../vectools.h. The bodies currently return WRONG-but-compiling
// placeholders — that is why `make test` is RED right now. Your job: turn it GREEN
// by implementing the real logic.
//
// make build compile your code (should already work)
// make test grade it (RED until you fill in the tasks)
// make solution run the reference if you get stuck
//
// THE PATTERN YOU ARE BUILDING TOWARDS:
// Every "hand*" function walks the vector with an explicit iterator loop —
// the same loop the standard library runs internally. Every "algo*" function
// delegates to a standard <algorithm>, using a free function as the
// predicate/comparator (NO lambdas — those arrive in Chapter 20).
//
// ITERATOR MENTAL MODEL (notes 18.2):
//
// auto it { vec.begin() }; // cursor pointing at the FIRST element
//
// vec.begin() vec.end()
// | |
// v v
// [ 3 ][ 1 ][ 4 ][ 1 ] one-past-last <-- DO NOT dereference end()!
//
// *it -> value at the cursor (dereference)
// ++it -> advance cursor by one
// it != end -> test: are we still inside the range?
//
// CS6340 / LLVM LENS:
// LLVM passes iterate over BasicBlock::iterator, Function::iterator, etc.
// The identical begin()/end()/++it/!= pattern you drill here shows up verbatim
// in every real pass. Being fluent now means you can read LLVM source code
// without stopping.
#include "../vectools.h"
#include <algorithm> // std::find, std::count_if, std::sort, std::max_element
#include <iterator> // std::distance
namespace vectools
{
// ─── TASK 1a: isEven — free-function predicate ────────────────────────────
// Return true if n is even (divisible by 2).
// Used as the predicate argument to std::count_if in algoCountEven.
// NO lambdas — this IS the Chapter 18 way to pass behaviour to an algorithm.
//
// >>> YOUR CODE HERE <<<
//
bool isEven(int /*n*/)
{
return false; // placeholder — always says "not even"
}
// ─────────────────────────────────────────────────────────────────────────
// ─── TASK 1b: greaterThan — free-function comparator ──────────────────────
// Return true iff a > b (a should come BEFORE b in descending order).
// Passed to std::sort as a strict-weak-ordering comparator.
//
// IMPORTANT: do NOT use `>=` or `<=` — that would violate strict weak
// ordering (equal elements would each claim to come before the other), which
// is undefined behaviour in std::sort. Use strictly `>`.
//
// >>> YOUR CODE HERE <<<
//
bool greaterThan(int /*a*/, int /*b*/)
{
return false; // placeholder — always says "no swap needed" (no ordering)
}
// ─────────────────────────────────────────────────────────────────────────
// ─── TASK 2a: handFind — explicit iterator loop ────────────────────────────
// Search `vec` for `target` using an iterator loop. Return the 0-based index
// of the first match, or -1 if not found.
//
// Skeleton:
// auto it { vec.begin() };
// while (it != vec.end()) {
// if (*it == target) return <index>;
// ++it;
// }
// return -1;
//
// To get the index from an iterator, use std::distance(vec.begin(), it),
// which counts how many steps from begin to it.
//
// DO NOT call std::find or any other <algorithm> here.
//
// >>> YOUR CODE HERE <<<
//
int handFind(const std::vector<int>& /*vec*/, int /*target*/)
{
return -1; // placeholder — always says "not found"
}
// ─────────────────────────────────────────────────────────────────────────
// ─── TASK 2b: algoFind — wrapping std::find ────────────────────────────────
// Use std::find(vec.begin(), vec.end(), target) to search.
// std::find returns an ITERATOR — you must:
// 1. Check it against vec.end() (not-found case).
// 2. If found, convert to an index: std::distance(vec.begin(), it).
//
// >>> YOUR CODE HERE <<<
//
int algoFind(const std::vector<int>& /*vec*/, int /*target*/)
{
return -1; // placeholder
}
// ─────────────────────────────────────────────────────────────────────────
// ─── TASK 3a: handCountEven — iterator loop ────────────────────────────────
// Count elements where *it % 2 == 0 using an explicit iterator loop.
// DO NOT call std::count_if or any other <algorithm>.
//
// >>> YOUR CODE HERE <<<
//
int handCountEven(const std::vector<int>& /*vec*/)
{
return 0; // placeholder — always says "zero even elements"
}
// ─────────────────────────────────────────────────────────────────────────
// ─── TASK 3b: algoCountEven — wrapping std::count_if ─────────────────────
// Use: std::count_if(vec.begin(), vec.end(), vectools::isEven)
//
// Pass the FREE FUNCTION isEven as the predicate (no lambdas!).
// std::count_if returns a std::ptrdiff_t; cast it to int before returning.
//
// >>> YOUR CODE HERE <<<
//
int algoCountEven(const std::vector<int>& /*vec*/)
{
return 0; // placeholder
}
// ─────────────────────────────────────────────────────────────────────────
// ─── TASK 4a: sortAscending — std::sort, default ordering ─────────────────
// Sort vec in ascending order using std::sort(vec.begin(), vec.end()).
// The default comparator uses operator< — smallest element goes first.
//
// >>> YOUR CODE HERE <<<
//
void sortAscending(std::vector<int>& /*vec*/)
{
// placeholder: does nothing — vec stays unsorted
}
// ─────────────────────────────────────────────────────────────────────────
// ─── TASK 4b: sortDescending — std::sort with free-function comparator ────
// Sort vec in descending order using std::sort with the greaterThan
// comparator declared in this namespace:
// std::sort(vec.begin(), vec.end(), vectools::greaterThan)
//
// greaterThan(a, b) returns true iff a > b — std::sort puts `a` before `b`
// when the comparator says "a should come first". Passing greaterThan
// therefore puts larger elements first.
//
// >>> YOUR CODE HERE <<<
//
void sortDescending(std::vector<int>& /*vec*/)
{
// placeholder: does nothing — vec stays unsorted
}
// ─────────────────────────────────────────────────────────────────────────
// ─── TASK 5: largest — std::max_element, with empty-range guard ──────────
// Return the largest element in vec, or 0 if vec is empty.
//
// EMPTY-RANGE TRAP (notes 18.2):
// auto it { std::max_element(vec.begin(), vec.end()) };
// if (it == vec.end()) return 0; // MUST check before dereferencing!
// return *it;
//
// std::max_element returns vec.end() when the range is empty. Dereferencing
// end() is undefined behaviour — always guard against it.
//
// >>> YOUR CODE HERE <<<
//
int largest(const std::vector<int>& /*vec*/)
{
return 0; // placeholder — happens to be correct ONLY for empty vector
}
// ─────────────────────────────────────────────────────────────────────────
} // namespace vectools
Try the lab first — the learning is in the attempt.
// Chapter 18 — Iterators and Algorithms · Project: VecTools (REFERENCE SOLUTION)
// ─────────────────────────────────────────────────────────────────────────────
// One complete, correct, warning-clean implementation of ../vectools.h.
// Peek only after you've taken a real swing at starter/vectools.cpp — the
// learning is in writing the explicit iterator loops yourself, then comparing
// them against the identical logic that the standard library hides inside
// std::find, std::count_if, etc.
//
// KEY OBSERVATION: every "hand*" loop here is exactly what the "algo*" version
// executes internally. The algorithms are not magic — they are the same cursor
// advancing, the same dereference, the same end() check, just packaged with a
// name. Knowing that makes the standard library readable instead of opaque.
#include "../vectools.h"
#include <algorithm> // std::find, std::count_if, std::sort, std::max_element
#include <iterator> // std::distance
namespace vectools
{
// ─── isEven — predicate passed to std::count_if ───────────────────────────
// A free function, not a lambda (lambdas are Chapter 20). The function
// pointer `vectools::isEven` decays to a bool(*)(int) which std::count_if
// accepts as its Predicate argument.
bool isEven(int n)
{
return n % 2 == 0;
}
// ─── greaterThan — comparator passed to std::sort ─────────────────────────
// Returns true iff `a` should come before `b` in the sorted sequence.
// For descending order: `a` comes first when `a` is larger.
//
// STRICT WEAK ORDERING: the comparator MUST satisfy these rules for std::sort
// to behave correctly (undefined behaviour otherwise):
// • irreflexivity: greaterThan(x, x) == false (an element is not > itself)
// • asymmetry: if greaterThan(a,b) then !greaterThan(b,a)
// • transitivity: if gt(a,b) && gt(b,c) then gt(a,c)
// Using `a > b` (strictly greater, NOT >=) satisfies all three.
bool greaterThan(int a, int b)
{
return a > b;
}
// ─── TASK 2a: handFind — the iterator loop in full view ──────────────────
// This is the loop that std::find runs internally. Writing it out makes the
// abstraction physical: `it` is a cursor; `*it` is the box it points at;
// `++it` moves it one step to the right; `it != vec.end()` tells us we
// haven't walked off the end.
int handFind(const std::vector<int>& vec, int target)
{
for (auto it { vec.begin() }; it != vec.end(); ++it)
{
if (*it == target)
{
// std::distance counts the steps from begin to `it`, giving us
// the 0-based index. (For vector iterators this is O(1) because
// they support random access, but the general form works for any
// forward iterator.)
return static_cast<int>(std::distance(vec.begin(), it));
}
}
return -1; // not found
}
// ─── TASK 2b: algoFind — delegating to std::find ─────────────────────────
// std::find returns an iterator, not an index. The two-step pattern:
// 1. get the iterator;
// 2. check for not-found BEFORE dereferencing (notes 18.2).
// Never dereference a vec.end() iterator — it has no element behind it.
int algoFind(const std::vector<int>& vec, int target)
{
auto it { std::find(vec.begin(), vec.end(), target) };
if (it == vec.end())
return -1; // not found — do NOT dereference here
return static_cast<int>(std::distance(vec.begin(), it));
}
// ─── TASK 3a: handCountEven — counting loop with explicit iterators ───────
// The predicate test (`*it % 2 == 0`) is inlined here. Compare with
// algoCountEven: they encode the same logic, one visible, one named.
int handCountEven(const std::vector<int>& vec)
{
int count { 0 };
for (auto it { vec.begin() }; it != vec.end(); ++it)
{
if (*it % 2 == 0)
++count;
}
return count;
}
// ─── TASK 3b: algoCountEven — delegating to std::count_if ────────────────
// isEven is a plain free function; passing it as `vectools::isEven` decays
// to a function pointer — the Chapter 18 way. std::count_if returns
// std::ptrdiff_t (a signed integer type); cast to int for the API.
int algoCountEven(const std::vector<int>& vec)
{
return static_cast<int>(
std::count_if(vec.begin(), vec.end(), vectools::isEven));
}
// ─── TASK 4a: sortAscending — default std::sort (uses operator<) ─────────
// The default comparator is std::less<int>, which uses `<`. Elements move
// left until the sequence satisfies "for all adjacent pairs: left <= right."
void sortAscending(std::vector<int>& vec)
{
std::sort(vec.begin(), vec.end());
}
// ─── TASK 4b: sortDescending — std::sort with greaterThan ────────────────
// Passing greaterThan flips the ordering rule: std::sort places `a` before
// `b` whenever greaterThan(a, b) is true, i.e. whenever a > b — so the
// largest element ends up at the front.
void sortDescending(std::vector<int>& vec)
{
std::sort(vec.begin(), vec.end(), vectools::greaterThan);
}
// ─── TASK 5: largest — std::max_element with empty-range guard ───────────
// std::max_element scans the half-open range [begin, end) and returns an
// iterator to the maximum element — or vec.end() if the range is empty.
//
// THE TRAP: dereferencing vec.end() is undefined behaviour. Always check
// the returned iterator before using it. The documented fallback here is 0.
//
// CS6340 PARALLEL: when scanning a BasicBlock for its heaviest instruction,
// the pass must guard against an empty block in exactly the same way.
int largest(const std::vector<int>& vec)
{
auto it { std::max_element(vec.begin(), vec.end()) };
if (it == vec.end())
return 0; // empty vector — no maximum exists; return fallback
return *it; // dereference is safe: we confirmed it != end()
}
} // namespace vectools
// Chapter 18 — Iterators and Algorithms · Project: VecTools (GRADER)
// ─────────────────────────────────────────────────────────────────────────────
// A tiny no-framework unit-test harness (same style as drills/CLAUDE.md spec).
// It includes ../vectools.h and calls the API through the vectools:: namespace.
//
// The Makefile links this file against starter/vectools.cpp (`make test`) or
// solution/vectools.cpp (`make test-solution`).
//
// WHAT PASSES: the solution. WHAT FAILS: the starter (all stubs return 0/-1).
//
// TESTING STRATEGY — each pair of hand/algo functions is checked to AGREE on
// the same inputs. If they disagree, one of them is wrong. Edge cases tested:
// • empty vector (all functions must handle it gracefully)
// • single-element vector (boundary: begin == one past last element)
// • duplicate values (find returns the FIRST; count counts all)
// • all-even / all-odd vectors (count edge)
// • negative numbers (isEven works for negatives: -4 % 2 == 0)
// • already-sorted input (sort must not corrupt it)
// • all-same elements (sort of equal values; greaterThan must not
// claim equal elements are > each other)
#include <iostream>
#include <vector>
#include "../vectools.h"
static int fails = 0;
// CHECK: assert a boolean condition; on failure, report the expression and line.
#define CHECK(cond) \
do { if(!(cond)){ std::cerr << "FAIL: " #cond " @line " << __LINE__ << "\n"; ++fails; } } while(0)
int main()
{
// =========================================================================
// Task 1 — isEven (free-function predicate)
// =========================================================================
CHECK(vectools::isEven(0) == true); // 0 is even
CHECK(vectools::isEven(2) == true);
CHECK(vectools::isEven(4) == true);
CHECK(vectools::isEven(-4) == true); // edge: negative even
CHECK(vectools::isEven(1) == false);
CHECK(vectools::isEven(3) == false);
CHECK(vectools::isEven(-3) == false); // edge: negative odd
// =========================================================================
// Task 1 — greaterThan (free-function comparator)
// =========================================================================
CHECK(vectools::greaterThan(5, 3) == true);
CHECK(vectools::greaterThan(3, 5) == false);
CHECK(vectools::greaterThan(5, 5) == false); // CRITICAL: equal -> false
// (strict weak ordering)
CHECK(vectools::greaterThan(0, -1) == true); // edge: negative
CHECK(vectools::greaterThan(-1, 0) == false);
// =========================================================================
// Task 2 — handFind and algoFind
// =========================================================================
{
std::vector<int> v { 3, 1, 4, 1, 5, 9, 2, 6 };
// basic finds
CHECK(vectools::handFind(v, 3) == 0); // first element
CHECK(vectools::algoFind(v, 3) == 0);
CHECK(vectools::handFind(v, 9) == 5); // middle element
CHECK(vectools::algoFind(v, 9) == 5);
CHECK(vectools::handFind(v, 6) == 7); // last element
CHECK(vectools::algoFind(v, 6) == 7);
// not found
CHECK(vectools::handFind(v, 7) == -1);
CHECK(vectools::algoFind(v, 7) == -1);
// duplicates: must return the FIRST occurrence
CHECK(vectools::handFind(v, 1) == 1); // two 1s; first is index 1
CHECK(vectools::algoFind(v, 1) == 1);
// hand and algo must agree on every value
for (int x : { 3, 1, 4, 5, 9, 2, 6, 7, 0 })
CHECK(vectools::handFind(v, x) == vectools::algoFind(v, x));
}
{
// empty vector — not found
std::vector<int> empty {};
CHECK(vectools::handFind(empty, 1) == -1);
CHECK(vectools::algoFind(empty, 1) == -1);
// single-element vector
std::vector<int> one { 42 };
CHECK(vectools::handFind(one, 42) == 0);
CHECK(vectools::algoFind(one, 42) == 0);
CHECK(vectools::handFind(one, 99) == -1);
CHECK(vectools::algoFind(one, 99) == -1);
}
// =========================================================================
// Task 3 — handCountEven and algoCountEven
// =========================================================================
{
std::vector<int> v { 1, 2, 3, 4, 5, 6 }; // three even: 2,4,6
CHECK(vectools::handCountEven(v) == 3);
CHECK(vectools::algoCountEven(v) == 3);
CHECK(vectools::handCountEven(v) == vectools::algoCountEven(v));
}
{
// all even
std::vector<int> allEven { 2, 4, 6, 8 };
CHECK(vectools::handCountEven(allEven) == 4);
CHECK(vectools::algoCountEven(allEven) == 4);
}
{
// all odd
std::vector<int> allOdd { 1, 3, 5, 7 };
CHECK(vectools::handCountEven(allOdd) == 0);
CHECK(vectools::algoCountEven(allOdd) == 0);
}
{
// empty vector
std::vector<int> empty {};
CHECK(vectools::handCountEven(empty) == 0);
CHECK(vectools::algoCountEven(empty) == 0);
}
{
// single element
std::vector<int> one { 8 };
CHECK(vectools::handCountEven(one) == 1);
CHECK(vectools::algoCountEven(one) == 1);
std::vector<int> oneOdd { 7 };
CHECK(vectools::handCountEven(oneOdd) == 0);
CHECK(vectools::algoCountEven(oneOdd) == 0);
}
{
// negative numbers: -4 is even, -3 is odd
std::vector<int> negs { -4, -3, -2, -1, 0 };
// even: -4, -2, 0 -> count 3
CHECK(vectools::handCountEven(negs) == 3);
CHECK(vectools::algoCountEven(negs) == 3);
CHECK(vectools::handCountEven(negs) == vectools::algoCountEven(negs));
}
// =========================================================================
// Task 4 — sortAscending and sortDescending
// =========================================================================
{
std::vector<int> v { 9, 1, 4, 7, 2 };
vectools::sortAscending(v);
// must be fully sorted ascending
CHECK(v[0] == 1);
CHECK(v[1] == 2);
CHECK(v[2] == 4);
CHECK(v[3] == 7);
CHECK(v[4] == 9);
}
{
std::vector<int> v { 9, 1, 4, 7, 2 };
vectools::sortDescending(v);
// must be fully sorted descending
CHECK(v[0] == 9);
CHECK(v[1] == 7);
CHECK(v[2] == 4);
CHECK(v[3] == 2);
CHECK(v[4] == 1);
}
{
// already sorted ascending — must remain ascending
std::vector<int> asc { 1, 2, 3, 4, 5 };
vectools::sortAscending(asc);
CHECK(asc[0] == 1 && asc[4] == 5);
// already sorted descending — must remain descending
std::vector<int> desc { 5, 4, 3, 2, 1 };
vectools::sortDescending(desc);
CHECK(desc[0] == 5 && desc[4] == 1);
}
{
// all same elements — sort should not corrupt the vector
std::vector<int> same { 7, 7, 7 };
vectools::sortAscending(same);
CHECK(same[0] == 7 && same[1] == 7 && same[2] == 7);
vectools::sortDescending(same);
CHECK(same[0] == 7 && same[1] == 7 && same[2] == 7);
}
{
// single element
std::vector<int> one { 42 };
vectools::sortAscending(one);
CHECK(one[0] == 42);
vectools::sortDescending(one);
CHECK(one[0] == 42);
}
{
// empty vector — both sorts must be no-ops
std::vector<int> empty {};
vectools::sortAscending(empty);
CHECK(empty.empty());
vectools::sortDescending(empty);
CHECK(empty.empty());
}
// =========================================================================
// Task 5 — largest
// =========================================================================
{
std::vector<int> v { 3, 1, 4, 1, 5, 9, 2, 6 };
CHECK(vectools::largest(v) == 9);
}
{
// single element
std::vector<int> one { 42 };
CHECK(vectools::largest(one) == 42);
}
{
// EMPTY VECTOR: must return 0 (documented fallback), not crash
std::vector<int> empty {};
CHECK(vectools::largest(empty) == 0);
}
{
// all same
std::vector<int> same { 7, 7, 7 };
CHECK(vectools::largest(same) == 7);
}
{
// negative numbers — largest is still the one closest to 0
std::vector<int> negs { -5, -1, -3 };
CHECK(vectools::largest(negs) == -1);
}
{
// mixed: the only positive value is the largest
std::vector<int> mixed { -10, -3, 2, -7 };
CHECK(vectools::largest(mixed) == 2);
}
// =========================================================================
// Cross-checks: hand* and algo* must agree on every shared input
// =========================================================================
{
std::vector<int> v { 10, 3, 8, 2, 6, 1, 4 };
CHECK(vectools::handFind(v, 8) == vectools::algoFind(v, 8));
CHECK(vectools::handFind(v, 99) == vectools::algoFind(v, 99));
CHECK(vectools::handCountEven(v) == vectools::algoCountEven(v));
}
// =========================================================================
// Result summary
// =========================================================================
if (!fails)
std::cout << "PASS \xe2\x9c\x85 all vectools checks passed.\n";
else
std::cerr << "\nFAIL \xe2\x9d\x8c " << fails
<< " check(s) failed — fix the TASK blocks in vectools.cpp.\n";
return fails ? 1 : 0;
}
# Chapter 18 — Iterators and Algorithms · VecTools · unit-test grader (Style B).
# Targets follow the drills/CLAUDE.md Makefile contract. TABS, not spaces.
#
# The learner implements ../vectools.h in starter/vectools.cpp.
# The grader (tests/tests.cpp) calls every vectools:: function across many
# inputs — including edge cases — and checks hand* vs algo* agree.
#
# -I. puts the chapter root on the include path so every file can write
# #include "vectools.h" by its simple name.
CXX := clang++
CXXFLAGS := -std=c++17 -Wall -Wextra -I.
.PHONY: all build run test solution test-solution clean
all: build
# ── Starter: compile-check the learner's vectools (warning-clean object) ────
build:
$(CXX) $(CXXFLAGS) -c starter/vectools.cpp -o starter/vectools.o
@echo "OK \xe2\x9c\x85 starter/vectools.cpp compiles. Now run: make test"
# run — no interactive program for this lab; print a helpful note instead.
run: build
@echo "This lab has no interactive driver. Use 'make test' to grade, or 'make solution' to run the reference."
# ── Grader: link tests against the LEARNER's starter/vectools.cpp ───────────
# RED until the TASK blocks are filled in; GREEN once they are.
test:
$(CXX) $(CXXFLAGS) tests/tests.cpp starter/vectools.cpp -o tests/run
@./tests/run || echo "FAIL \xe2\x9d\x8c fill in the TASK blocks in starter/vectools.cpp until every check passes."
# ── Reference solution ───────────────────────────────────────────────────────
solution:
$(CXX) $(CXXFLAGS) tests/tests.cpp solution/vectools.cpp -o tests/run_sol
@./tests/run_sol
# ── Proof the exercise is solvable: tests + solution MUST be green ───────────
test-solution:
$(CXX) $(CXXFLAGS) tests/tests.cpp solution/vectools.cpp -o tests/run_sol
@./tests/run_sol
clean:
rm -f starter/vectools.o tests/run tests/run_sol
rm -rf tests/run.dSYM tests/run_sol.dSYM
// ============================================================================
// vectools.h — the PUBLIC INTERFACE of the vectools library (Chapter 18)
// ----------------------------------------------------------------------------
// This header is COMPLETE and PROVIDED. Do not edit it. The grader, the demo,
// the starter, and the solution all include it. Changing a signature will
// break the build.
//
// WHAT THIS LIBRARY DEMONSTRATES — the "two ways" pattern:
// For each operation you write TWO versions that MUST agree on every input:
//
// hand* — written with explicit iterator objects (.begin() / .end() /
// ++it / *it) so you SEE the abstraction at work.
// algo* — delegates to a standard <algorithm>, with free-function
// predicates/comparators (no lambdas — those arrive in Ch 20).
//
// Seeing both forms side-by-side is the central lesson of Chapter 18: iterators
// are the VOCABULARY that algorithms speak, and the standard library is using
// exactly the same loop you would have written by hand — just packaged and named.
//
// FREE-FUNCTION PREDICATES (not lambdas):
// The <algorithm> functions that accept a callable (count_if, sort, …) take
// any callable. Chapter 18 teaches them via free functions (plain C++ function
// pointers) — e.g. `bool isEven(int n)`. Lambdas are the natural next step,
// but they are not introduced until Chapter 20. This file therefore declares
// two free-function predicates/comparators in namespace vectools — those are
// ALSO tasks the learner implements.
//
// CS6340 / LLVM LENS:
// LLVM's pass infrastructure passes collections of instructions, basic blocks,
// and functions as ranges. The same `begin()` / `end()` / algorithm idioms you
// practice here appear verbatim in real LLVM passes. Being fluent now means you
// can read and write that code without hesitation in Lab 1.
//
// Header guard (Chapter 2): prevents double inclusion.
#ifndef VECTOOLS_H
#define VECTOOLS_H
#include <vector> // std::vector (Chapter 16)
#include <cstddef> // std::ptrdiff_t
namespace vectools
{
// =========================================================================
// FREE-FUNCTION PREDICATES / COMPARATORS (Tasks 1a and 1b)
// -------------------------------------------------------------------------
// These are passed BY POINTER to std::count_if and std::sort respectively.
// They must be FREE FUNCTIONS (not lambdas, not functors) — Ch 20 introduces
// lambdas; here we use the plain function-pointer form.
// =========================================================================
// ── TASK 1a predicate ────────────────────────────────────────────────────
// isEven — return true iff n is divisible by 2.
// Passed as a function pointer to std::count_if in algoCountEven (Task 3b).
bool isEven(int n);
// ── TASK 1b comparator ───────────────────────────────────────────────────
// greaterThan — return true iff a should come BEFORE b in descending order.
// A valid strict-weak-ordering comparator: returns true iff a > b.
// Passed as a function pointer to std::sort in sortDescending (Task 4b).
// IMPORTANT: "less than or equal" (<=) would violate strict weak ordering;
// use strictly greater than (>).
bool greaterThan(int a, int b);
// =========================================================================
// FIND (Tasks 2a & 2b)
// -------------------------------------------------------------------------
// Search vec for the first occurrence of `target`.
// Returns the 0-based INDEX if found, or -1 if not found.
// (Returning an index is clean and avoids exposing iterator internals in
// the public API; internally the implementations use iterators.)
// =========================================================================
// ── TASK 2a: hand-written search with explicit iterators ─────────────────
// Write your own loop with:
// auto it { vec.begin() }; … it != vec.end(); ++it … *it
// Do NOT call std::find or any other <algorithm>.
int handFind(const std::vector<int>& vec, int target);
// ── TASK 2b: algorithm search wrapping std::find ─────────────────────────
// Use: std::find(vec.begin(), vec.end(), target)
// Check the returned iterator against vec.end() to distinguish found/not-found.
// Convert the iterator to an index with std::distance(vec.begin(), it).
int algoFind(const std::vector<int>& vec, int target);
// =========================================================================
// COUNT EVEN (Tasks 3a & 3b)
// -------------------------------------------------------------------------
// Count how many elements in vec are even (divisible by 2).
// =========================================================================
// ── TASK 3a: hand-written count with explicit iterators ──────────────────
// Iterate with the iterator loop pattern; test each *it with `% 2 == 0`.
// Do NOT call std::count_if or any other <algorithm>.
int handCountEven(const std::vector<int>& vec);
// ── TASK 3b: algorithm count using std::count_if + free function ─────────
// Use: std::count_if(vec.begin(), vec.end(), vectools::isEven)
// Pass the FREE FUNCTION `isEven` (declared above) as the predicate.
// NO lambdas — those are Chapter 20.
int algoCountEven(const std::vector<int>& vec);
// =========================================================================
// SORT (Tasks 4a & 4b)
// -------------------------------------------------------------------------
// Both functions sort IN PLACE (modify the vector passed by reference).
// =========================================================================
// ── TASK 4a: ascending sort ───────────────────────────────────────────────
// Use: std::sort(vec.begin(), vec.end()) — the default uses operator<.
void sortAscending(std::vector<int>& vec);
// ── TASK 4b: descending sort with free-function comparator ───────────────
// Use: std::sort(vec.begin(), vec.end(), vectools::greaterThan)
// Pass the FREE FUNCTION `greaterThan` (declared above) as the comparator.
// greaterThan(a,b) returns true iff a > b — std::sort puts `a` before `b`
// when the comparator says a "comes first".
void sortDescending(std::vector<int>& vec);
// =========================================================================
// LARGEST (Task 5)
// -------------------------------------------------------------------------
// Return the largest element in vec, or 0 if vec is empty.
//
// Use std::max_element(vec.begin(), vec.end()).
// EMPTY-RANGE TRAP: std::max_element returns vec.end() when the range is
// empty. You MUST check for that before dereferencing. The documented
// fallback for an empty vector is 0.
// =========================================================================
// ── TASK 5: largest element via std::max_element ─────────────────────────
int largest(const std::vector<int>& vec);
} // namespace vectools
#endif // VECTOOLS_H
make test locally
(see “Build & run locally” above).