Skip to content

CT 301 Homework 1

CT 301: C++ Fundamentals · Summer 2026
Homework 1: NUI Hardware Inventory System
100 points · Instructor: Dr. Francisco R. Ortega

1. Overview

You will implement an object-oriented inventory system for managing hardware assets, like what a research lab or IT department uses to track equipment. Items can be loaned out (and, you hope, returned), sold, or surplused (retired). The program reads an initial inventory and a list of transactions from files, applies them, writes the final inventory state to a file, and prints reports to the console.

The system is built around one idea: operations select a collection of items, and you then print or act on that collection. Selecting all Razer devices, all equipment older than a date, or the ten most valuable items each produces a collection; printing it, surplusing it, or loaning it are things you do to that collection afterward.

This assignment is about object-oriented design, polymorphism, and memory ownership with smart pointers. You will own items with std::unique_ptr, transfer ownership with std::move, overload operators, and implement selection and aggregation algorithms.

You may not use raw owning pointers, new, or delete anywhere in this assignment. Raw pointers and manual memory management are the subject of Homework 2. You may use the C++ standard library freely.

2. What We Provide, What You Implement

You are given a set of header and source files. Do not modify the provided files.

Provided (plumbing, so you can focus on the design work):

  • The interfaces: item.h, inventory.h, command.h, state.h, loan_record.h.
  • The file reader, tokenizer, and the two factories (make_item, make_command) that turn input lines into objects, with full error handling.
  • Date::parse, the driver (main.cpp), and the makefile.
  • The std::formatter specializations, so your types also work with std::print.
  • The three private Inventory helpers (extract, gather, owningCollection) and one worked example, surplus, which shows the action pattern. In command.cpp, PrintCommand::execute is the worked example. Study these; the rest follow the same patterns.

You implement:

  • Date: the comparison operators and operator<<.
  • The three item classes: Equipment, Consumable, Borrowed.
  • The remaining Inventory operations (everything except the one worked example, surplus, and the three provided helpers).
  • The Command execute() bodies (each mostly calls the matching Inventory operation).

3. Learning Objectives

4. Classes

Class Role
Item (abstract)Polymorphic base for all items.
Equipment, Consumable, BorrowedConcrete item types deriving from Item.
DateValue type with overloaded comparison and << operators.
LoanRecordTies a borrowed item to a borrower and date.
Command (abstract) + derivedOne per transaction verb; runs via execute(RunContext&).
InventoryOwns all items; selection, action, and aggregation operations.
enum class StateInStock, OnLoan, Sold, Surplus.

4.1 Item hierarchy and identity

The abstract Item base has a virtual destructor and pure virtual functions: canLoan(), currentValue(), a generic attribute(key) used for selection, and a render hook printTo() used by operator<<.

Each item's unique ID is its serial number where one exists; items with no serial (a cable) get a minted tag such as CXY-01. Items of the same kind are grouped by shared attributes such as brand and model: ten identical laptops are ten items, ten serial IDs, same brand and model.

Equipment condition. A piece of equipment can independently be obsolete and/or broken, and has two vendor dates. These combine (an item can be both obsolete and broken at once), so they are flags, not a single state:

Each derived type answers attribute() for its own fields and returns nothing for a field it does not have.

Behavior differs by type:

bool Consumable::canLoan() const { return false; }      // a cable cannot be loaned
bool Equipment::canLoan() const  { return true; }       // a laptop can

// attribute() answers the keys this type has, else defers to the base:
std::optional<std::string> Equipment::attribute(std::string_view key) const {
    if (key == "brand")            return brand_;
    if (key == "cpu")              return cpu_;
    if (key == "broken")           return broken_ ? "true" : "false";
    if (key == "obsolete")         return obsolete_ ? "true" : "false";
    if (key == "norma")            return noRMA() ? "true" : "false";   // computed from warranty_end
    if (key == "surplus_candidate")return surplusCandidate() ? "true" : "false";
    return baseAttribute(key);          // id, name, category, state
}

4.2 Date

A value type (year, month, day). Date::parse (provided) reads YYYY-MM-DD. You write the comparison operators (==, !=, <, <=, >, >=) and operator<<.

Example:

Date a = Date::parse("2021-01-01");
Date b = Date::parse("2024-09-01");
if (a < b) { /* true: your operator< compares year, then month, then day */ }
std::cout << a;          // your operator<< prints: 2021-01-01

4.3 LoanRecord

A small record describing one active loan. The inventory creates a LoanRecord when an item is loaned, removes it when the item is returned, and reads it in report() to show who has what.

struct LoanRecord {
    std::string itemId;     // which item is out (its serial / id)
    std::string borrower;   // who has it
    Date        when;       // when it was loaned
};

Lifecycle: loan(...) appends a record; return_items(...) erases the matching record; report() prints one line per active record, e.g. SN1207 -> student_ramirez (2026-06-01).

4.4 Command hierarchy

Every transaction line is turned into a small Command object that knows how to run itself. This is how the program executes a file of transactions: the provided factory reads each line and builds the matching command, then the driver calls execute() on each one in order.

All commands run against a shared RunContext (provided), which carries the three things a command might touch:

struct RunContext {
    Inventory&          inv;         // the inventory being modified
    std::vector<Item*>  selection;   // the current selection (carried line to line)
    std::ostream&       out;         // where PRINT / REPORT write
};

A concrete RunContext midway through a run (after BY brand Razer selected two items):

// inv      -> the one Inventory holding all items
// selection -> { pointer to SN-KB1 (a Razer keyboard),
//               pointer to SN-MS1 (a Razer mouse) }   // 2 borrowed pointers
// out       -> std::cout
//
// A following PRINT prints those two items; a following SURPLUS
// surpluses them and replaces selection with whatever it rejected.

The command classes are declared for you; you write only their execute() bodies, and each is tiny because it just calls the matching Inventory operation. The whole chain for one line looks like this:

// 1. The transaction line:        SURPLUS
// 2. The factory builds:           std::make_unique<SurplusCommand>()
// 3. The driver runs:              cmd->execute(ctx);

// 4. The execute() body you write (one line):
void SurplusCommand::execute(RunContext& ctx) {
    ctx.selection = ctx.inv.surplus(ctx.selection);   // act, keep the rejects as the selection
}

// A selection command is just as small:
void SelectByCommand::execute(RunContext& ctx) {
    ctx.selection = ctx.inv.select_by(key_, value_);
}

A transaction file, then, is a sequence of these commands sharing one RunContext: a selection command sets ctx.selection, then a later PrintCommand or SurplusCommand uses it.

4.5 Rendering with operator<<

Items and dates render through operator<<. For Date you write the operator directly. For items, a single operator<<(std::ostream&, const Item&) is provided; it calls the virtual printTo(), which you implement in each derived type so it prints that type's serial and fields. Figuring out what each line must contain to match the expected output is part of the assignment (the provided sample shows the target). You do not write any std::formatter; we provide it.

The provided operator<< calls your printTo:

// provided in item.h:
std::ostream& operator<<(std::ostream& os, const Item& it) { it.printTo(os); return os; }

// you write printTo in each derived type so it prints that type's fields:
void Equipment::printTo(std::ostream& os) const {
    os << id_ << " EQUIPMENT " << name_ << " ... value=" << currentValue();
}
// then this just works, dispatching to the right printTo:
Item* p = /* an Equipment */;   std::cout << *p;

5. Ownership: Who Owns, Who Borrows

A state change is a move, not a copy:

// loaning: move the owning unique_ptr from the in-stock vector into the on-loan vector
std::unique_ptr<Item> owned = extract(inStock_, id);   // remove + take ownership
owned->setState(State::OnLoan);
onLoan_.push_back(std::move(owned));                   // ownership transferred; no copy
// (unique_ptr cannot be copied, so the compiler forces you to move)

6. The Inventory Interface

These operations are declared in the provided inventory.h; you implement them (surplus is done for you). A selection returns a collection of borrowed (non-owning) pointers; output and action operations consume one. Each group is shown below with a short example.

6.1 Selection (returns borrowed, non-owning pointers)

Each selection walks the inventory and returns a std::vector<Item*> pointing at items the inventory still owns. group_by instead returns a map from an attribute value to its members. select_by is provided as a worked example.

std::vector<Item*> select_all();
std::vector<Item*> select_by(key, value);               // [WORKED EXAMPLE]
std::vector<Item*> select_in_state(State s);
std::vector<Item*> select_older_than(const Date& d);
std::vector<Item*> select_top_by_value(std::size_t n);
std::map<std::string, std::vector<Item*>> group_by(key);

Example:

std::vector<Item*> razer = inv.select_by("brand", "Razer");  // all Razer items
std::vector<Item*> top3  = inv.select_top_by_value(3);       // 3 most valuable in stock
// group_by returns buckets keyed by the attribute value:
auto byBrand = inv.group_by("brand");   // byBrand["Razer"] is a vector of Item*

6.2 Aggregation

These fold over the items to produce numbers. total_value sums the current value of any collection (provided as a worked example); the others summarize the whole inventory.

double                        total_value(const std::vector<Item*>& items) const;  // [WORKED EXAMPLE]
std::map<std::string, double> value_by_category() const;
std::map<State, std::size_t>  count_by_state() const;

Example:

double worth = inv.total_value(inv.select_in_state(State::InStock));  // value of all in-stock
auto perCat  = inv.value_by_category();   // perCat["peripherals"] == summed value
auto counts  = inv.count_by_state();      // counts[State::OnLoan] == number on loan

6.3 Action (returns the subset it could NOT act on)

An action changes the state of each item in the collection and returns the rejects, the items it could not act on (for example, an item that was not in the required starting state). add takes ownership of a new item. surplus is provided as a worked example.

void               add(std::unique_ptr<Item> item);                       // take ownership (move)
std::vector<Item*> surplus(const std::vector<Item*>& items);              // [WORKED EXAMPLE]
std::vector<Item*> sell(const std::vector<Item*>& items);
std::vector<Item*> loan(const std::vector<Item*>& items, borrower, when);
std::vector<Item*> return_items(const std::vector<Item*>& items);

Example:

auto rejects = inv.surplus(inv.select_older_than(Date::parse("2021-01-01")));
// every old in-stock item is now Surplus; rejects holds any that could not be
// surplused (e.g. one that was OnLoan), so you can print or handle them.
inv.add(std::make_unique<Equipment>(/* ... */));   // ownership moves into the inventory

6.4 Output

print is overloaded once per result shape, so you print whatever a selection or aggregation returned. print for a collection is provided as a worked example. report is the whole-inventory summary; write dumps the final state.

void print(const std::vector<Item*>& items, std::ostream& os) const;             // [WORKED EXAMPLE]
void print(const std::map<std::string, std::vector<Item*>>& groups, std::ostream&) const;
void print(const std::map<std::string, double>& table, std::ostream& os) const;
void report(std::ostream& os) const;
void write(std::ostream& os) const;   // final state of all items, sorted by id

Example:

inv.print(inv.select_by("brand", "Razer"), std::cout);   // list the Razer items
inv.print(inv.group_by("category"), std::cout);          // list each category group
inv.report(std::cout);                                   // the summary (see below)

What report() prints: counts by state (InStock, OnLoan, Sold, Surplus); total current value of in-stock items; total revenue from sold items; each item on loan with its borrower and date; and the surplus count.

Invariants you must enforce: an item is always in exactly one state; an item that is OnLoan cannot be sold or surplused; a Consumable can never be loaned; IDs are unique. (These hold on their own if each action accepts only items in the right starting state.)

7. Value Rules

Grading compares your output exactly, so currentValue() must follow these rules. The reference "today" is 2026-06-01.

Computed conditions (Equipment):

Examples:

// working, base 180.00, purchased 3 full years ago:
//   180.00 * (1 - 0.20*3) = 72.00      (floor would be 180.00 * 0.10 = 18.00)
// broken: value = 0.00 and canLoan() == false, regardless of age
// obsolete but working: value unchanged, still loanable (a Magic Leap 1)
// broken AND past warranty_end: NoRMA == true -> surplus candidate

8. Transaction Grammar (fixed)

The transactions file keeps a current selection. Selection verbs set it; output and action verbs use it. After an action, the current selection becomes the action's rejects, so a following PRINT lists what could not be done. Invalid lines are reported and skipped.

ADD <TYPE> <ID> <NAME> <CATEGORY> <VALUE> [type-specific fields...]

# selection (sets the current selection)
ALL
BY <KEY> <VALUE>
IN_STATE <STATE>
OLDER_THAN <DATE>
TOP_BY_VALUE <N>
GROUP_BY <KEY>        # prints the grouping

# output (uses the current selection)
PRINT
REPORT

# action (uses the current selection; leaves rejects as the selection)
SURPLUS
SELL
LOAN <BORROWER> <DATE>
RETURN

Example (surplus everything bought before 2021, then list what is still on loan):

OLDER_THAN 2021-01-01
SURPLUS
IN_STATE OnLoan
PRINT

9. Running the Program (Input / Output Contract)

Getting started. Build with the provided makefile, then run the program by giving it three file names on the command line:

make
./inventory data/appx_inventory.txt data/appx_transactions.txt out.txt

The three arguments, in this exact order, are:

  • inventory file — the starting items, one per line (read first; each becomes an item).
  • transactions file — the commands to apply, one per line, in order.
  • output file — the file your program writes the final inventory to. It is created or overwritten; do not create it yourself.

Two different things come out of the program:

  • Whatever PRINT, GROUP_BY, and REPORT produce is written to the screen (standard output) as each runs.
  • The final state of every item is written to the output file you named (this is what write does, at the very end).

Checking your work against the sample. A small example is provided with its expected output. Run it and compare:

./inventory data/appx_inventory.txt data/appx_transactions.txt myout.txt > mystdout.txt
diff myout.txt   data/appx_output.txt
diff mystdout.txt data/appx_stdout.txt

When both diffs print nothing, your output matches the sample. Matching the sample is necessary but not sufficient: test the rest of your code yourself (a starter test file, student_tests.cpp, is included).

Running your own tests. A starter harness, student_tests.cpp, is included. Build and run it like this (from the project folder):

g++ -std=c++26 -Wall -Wextra -Wpedantic -g student_tests.cpp date.cpp items.cpp \
    inventory.cpp command.cpp tokenizer.cpp file_reader.cpp factories.cpp -o student_tests
./student_tests

Add your own tests as you go. Your grade depends on correct behavior across cases you will not have seen, so test thoroughly.

Command line (fixed):

./inventory <inventory_file> <transactions_file> <output_file>

Inventory input, one item per line. The ID is the serial (or a minted tag). obsolete and broken are true or false; dates are YYYY-MM-DD. Use - for an absent optional field (for example a missing cpu or warranty date). Names use underscores for spaces.

EQUIPMENT  <serial> <name> <category> <value> <date> <brand> <model> <cpu> <obsolete> <broken> <warranty_end> <support_end>
CONSUMABLE <id>     <name> <category> <value> <quantity> <brand> <model>
BORROWED   <id>     <name> <category> <value> <lender> <returnby> <brand> <model>
EQUIPMENT  SN8842 BlackWidow_Keyboard peripherals 180.00 2023-02-10 Razer BlackWidow_V4 - false false 2026-02-10 2028-02-10
CONSUMABLE CXY-01 USB-C_Cable cables 8.50 50 - -
BORROWED   BR-7001 LoanerScope test-bench 0.00 PartnerLab 2026-09-01 Tektronix MDO3

A fully worked sample (sample input files and their exact expected output) is provided so you can match the line and report formatting precisely.

10. Constraints, Tooling, and Build

About the sanitizers. -fsanitize=address,undefined turns on two runtime checkers:

  • AddressSanitizer catches memory errors: leaks, use-after-free, out-of-bounds access.
  • UndefinedBehaviorSanitizer catches undefined behavior: signed overflow, null dereference, bad casts, and similar.

Your program must run with zero sanitizer reports. With correct unique_ptr use, items free themselves (RAII), so you write no delete and these checks pass for free.

11. Grading (100 points, fully automated)

Graded automatically. You may resubmit as many times as you like before the deadline. The autograder reports which checks failed so you can iterate.

Component Points
Compiles cleanly (provided makefile, no warnings)5
Runs clean under ASan/UBSan10
Bookkeeping actions correct (add, loan, return, sell, surplus)15
Selection, grouping, and aggregation correct25
Invariants enforced (illegal actions correctly rejected)15
Type-specific behavior and value rules correct15
Output and report correct and in the specified format15
Penalty: raw owning pointers, new, delete, malloc, or free in your sourceup to −20
The professor reserves the right to award or deduct points for any aspect of the assignment not explicitly listed above.

Requirements like a virtual destructor, correct unique_ptr use, and moving rather than copying are checked indirectly: get them wrong and the program fails to compile, fails the sanitizers, or produces wrong output. The raw-pointer prohibition is checked by scanning your source.

12. Use of AI

You may use AI tools. The exams are weighted heavily and will test whether you actually understand this material, so using AI to produce code you cannot explain will hurt you on the exams. Use AI the way you would use a knowledgeable tutor or documentation: to ask questions, clarify concepts, and check your reasoning, not to generate a solution you do not understand.

13. What to Submit

Submit through the course autograder:

Appendix A. Worked Example

A complete run of the system. Your program, given the two input files below, must produce exactly the console output and output file shown. Use this to calibrate your formatting; the autograder compares output this precisely.

A.1 Command line

./inventory appx_inventory.txt appx_transactions.txt appx_output.txt

A.2 Input: appx_inventory.txt

# type id name category value [type-specific fields...]
EQUIPMENT SN8842 BlackWidow_Keyboard peripherals 180.00 2023-02-10 Razer BlackWidow_V4 - false false 2026-02-10 2028-02-10
EQUIPMENT SN1207 DeathAdder_Mouse peripherals 70.00 2024-09-01 Razer DeathAdder_V3 - false false 2026-09-01 2027-09-01
EQUIPMENT SN5500 Oscilloscope test-bench 1200.00 2019-05-20 Keysight DSOX1204G - false true 2022-05-20 2024-05-20
CONSUMABLE CXY-01 USB-C_Cable cables 8.50 50 - -
BORROWED BR7001 LoanerScope test-bench 0.00 PartnerLab 2026-09-01 Tektronix MDO3

A.3 Input: appx_transactions.txt

# loan the Razer mouse to a student
BY id SN1207
LOAN student_ramirez 2026-06-01
# show the state of the lab
REPORT
# retire broken, out-of-warranty gear (the oscilloscope qualifies)
BY surplus_candidate true
SURPLUS
# list the Razer gear we still have
BY brand Razer
PRINT

A.4 Console output (stdout)

The REPORT summary, then the PRINT of the two Razer items still present (sorted by id). Note the depreciated values (180.00 to 72.00, 70.00 to 56.00) and that the broken oscilloscope was selected by BY surplus_candidate true and surplused.

=== Inventory Report ===
InStock: 4
OnLoan: 1
Sold: 0
Surplus: 0
In-stock value: 497.00
Revenue: 0.00
On loan:
  SN1207 -> student_ramirez (2026-06-01)
Surplus count: 0
SN1207 EQUIPMENT DeathAdder_Mouse peripherals Razer DeathAdder_V3 cpu=- purchased=2024-09-01 value=56.00 obsolete=false broken=false warranty=2026-09-01 support=2027-09-01 OnLoan
SN8842 EQUIPMENT BlackWidow_Keyboard peripherals Razer BlackWidow_V4 cpu=- purchased=2023-02-10 value=72.00 obsolete=false broken=false warranty=2026-02-10 support=2028-02-10 InStock

A.5 Output file: appx_output.txt

Every item, sorted by id, with its final state. The broken out-of-warranty oscilloscope (SN5500) is now Surplus; the mouse is OnLoan; the borrowed scope stays InStock (it can only be returned to its lender).

BR7001 BORROWED LoanerScope test-bench Tektronix MDO3 lender=PartnerLab returnby=2026-09-01 value=0.00 InStock
CXY-01 CONSUMABLE USB-C_Cable cables qty=50 value=425.00 InStock
SN1207 EQUIPMENT DeathAdder_Mouse peripherals Razer DeathAdder_V3 cpu=- purchased=2024-09-01 value=56.00 obsolete=false broken=false warranty=2026-09-01 support=2027-09-01 OnLoan
SN5500 EQUIPMENT Oscilloscope test-bench Keysight DSOX1204G cpu=- purchased=2019-05-20 value=0.00 obsolete=false broken=true warranty=2022-05-20 support=2024-05-20 Surplus
SN8842 EQUIPMENT BlackWidow_Keyboard peripherals Razer BlackWidow_V4 cpu=- purchased=2023-02-10 value=72.00 obsolete=false broken=false warranty=2026-02-10 support=2028-02-10 InStock