Categories: CS112 Β  CS113 Oct 26, 2026 β€’ Anika Marathe (based on John Mortensen’s lesson) β€’ 12 min read

Abstract Collectables & Dynamic Registries: Homework

Model the OCS survey’s Fantasy Persona as a Collectable, compose it into a FantasyStudent, and use a Registry to sort, order and group the roster. Purpose of my hack: ________ (update me)

Run the cells top to bottom (they share state). Save each runner (πŸ’Ύ) before submitting.


Reference Guide

Term Meaning In this lesson
Interface A contract of methods Collectable
Abstract class Shared code, cannot be instantiated AbstractCollectable (derives getKeys, getValue, toString from getMap)
Natural order Default order from compareTo Student by name, Standing by weight
Bounded generics / wildcard Type limited to a family of types Catalog<T extends Collectable>, Registry(Catalog<? extends Collectable>)
Map projection Fields exposed as key/value pairs getMap() with a LinkedHashMap
Decorator Wrapper that adds behavior ProjectedCollectable moves the sort key to column 1
Stable sort Equal items keep their original order List.sort

Standards: CS112-OOP-1/OOP-2 (interfaces, generics), CS112-DS-1 (LinkedHashMap, computeIfAbsent), CS112-ALG-1 (sorting), CS113-DS-2 (decorator), CS113-TEST-1 (edge cases), CS113-PBL-1 (domain entities to inspectable data).


Real-Life Application

Situation What went wrong What the pattern fixes
Roster sorted with plain String.compareTo isaac Newton lands after every capital letter One case-insensitive compareTo, reused everywhere
Table that only sorts its hard-coded column Every new column needs new code sortByField(key) sorts by any key
Survey dashboard grouping by persona, standing, pathway Copy-pasted loops groupBy(key) partitions any collection

Takeaway: write the record once (getMap, compareTo) and let the registry sort and group every record type.


Tech Talk

Learning objective: model data as Collectable records, store them in a type-safe Catalog, and process them with a Registry. Success criteria: I can write getMap() and compareTo() for a new class; sort naturally, sort by a key and group by a key; and test empty, singleton, duplicate and missing-key cases.

Student --getMap()--> {Name, Standing, Pathway Year, ...} --orderBy("Pathway Year")--> {Pathway Year, Name, Standing, ...}
Collectable (interface) <-- AbstractCollectable <-- Animal, Student, FantasyStudent, Pathway, Standing, FantasyPersona

A. Building blocks (provided, do not change)

// CODE_RUNNER: Run to compile Collectable, AbstractCollectable, Catalog and Registry. Read the comments: they are your reference.
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Collectable: the contract every row-like record must follow.
 * - Polymorphism: Registry/Catalog treat Student, Animal, Pathway... all as "Collectable".
 * - Extends Comparable<Collectable>: forces every record to define a natural order.
 * - getMap(): exposes fields as key/value pairs so code can ask for a field by *name*.
 */
interface Collectable extends Comparable<Collectable> {
    Map<String, Object> getMap();
    List<String> getKeys();
    Object getValue(String key);
}

/**
 * AbstractCollectable: shares code that every Collectable can derive from getMap().
 * Template Method idea: subclasses only supply getMap() and compareTo().
 */
abstract class AbstractCollectable implements Collectable {
    @Override public List<String> getKeys() { return new ArrayList<>(getMap().keySet()); }
    @Override public Object getValue(String key) { return getMap().get(key); }
    @Override public String toString() { return getMap().toString(); }
}

/** Catalog<T>: a type-safe container (like a database table) for any Collectable subtype. */
class Catalog<T extends Collectable> {
    private List<T> items = new ArrayList<>();
    public void add(T item) { items.add(item); }
    public List<T> getItems() { return items; }
    public void display() { for (T item : items) System.out.println(item); }
}

/**
 * Registry: sorting and grouping services over ANY catalog of Collectables.
 * - Wildcard generics (? extends Collectable) lets it accept Catalog<Student>, Catalog<Animal>, ...
 * - sortByField uses key strings, so one method sorts every kind of record.
 * - groupBy uses Map.computeIfAbsent to build "one list per category".
 */
class Registry {
    private List<Collectable> items;

    public Registry(Catalog<? extends Collectable> catalog) {
        this.items = new ArrayList<>(catalog.getItems());
    }

    /** Decorator: same data, but the primaryKey is moved to the first column. */
    public record ProjectedCollectable(Collectable delegate, String primaryKey) implements Collectable {
        @Override
        public Map<String, Object> getMap() {
            Map<String, Object> original = delegate.getMap();
            Map<String, Object> reordered = new LinkedHashMap<>();
            if (original.containsKey(primaryKey)) {
                reordered.put(primaryKey, original.get(primaryKey));
            }
            original.forEach((k, v) -> {
                if (!k.equals(primaryKey)) reordered.put(k, v);
            });
            return reordered;
        }
        @Override public List<String> getKeys() { return getMap().keySet().stream().toList(); }
        @Override public Object getValue(String key) { return getMap().get(key); }
        @Override public int compareTo(Collectable o) { return delegate.compareTo(o); }
        @Override public String toString() { return getMap().toString(); }
    }

    public List<Collectable> sortNatural() {
        List<Collectable> sorted = new ArrayList<>(items);
        Collections.sort(sorted);
        return sorted;
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    public List<Collectable> sortByField(String key) {
        List<Collectable> sorted = new ArrayList<>(items);
        sorted.sort((a, b) -> {
            Object valA = a.getValue(key);
            Object valB = b.getValue(key);
            if (valA instanceof Comparable && valB instanceof Comparable) {
                return ((Comparable) valA).compareTo(valB);
            }
            return 0;
        });
        return sorted;
    }

    public List<ProjectedCollectable> orderBy(String key) {
        return sortByField(key).stream()
                .map(item -> new ProjectedCollectable(item, key))
                .toList();
    }

    public Map<Object, List<Collectable>> groupBy(String key) {
        Map<Object, List<Collectable>> groupedMap = new LinkedHashMap<>();
        for (Collectable item : items) {
            Object groupKey = item.getValue(key);
            groupedMap.computeIfAbsent(groupKey, k -> new ArrayList<>()).add(item);
        }
        return groupedMap;
    }
}

B. Simple data set: Animals

Run the demo and match each output section to Animals.main.

Code Runner Challenge

Run the Animals demo: display, natural sort, orderBy Age, groupBy Species.

View IPYNB Source
// CODE_RUNNER: Run the Animals demo: display, natural sort, orderBy Age, groupBy Species.
import java.util.*;
import java.util.*;

/** Animal (from the lesson): used here to prove the comparator works across different Collectable types. */
class Animal extends AbstractCollectable {
    private String name;
    private String species;
    private int age;

    public Animal(String name, String species, int age) {
        this.name = name;
        this.species = species;
        this.age = age;
    }

    @Override
    public Map<String, Object> getMap() {
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("Name", name);
        map.put("Species", species);
        map.put("Age", age);
        return map;
    }

    @Override
    public int compareTo(Collectable o) { return this.name.compareTo((String) o.getValue("Name")); }
}

/** Animals: demo driver for the simple Animal data set (no Student code needed). */
class Animals {
    public static void main(String[] args) {
        Catalog<Animal> animalCatalog = new Catalog<>();
        animalCatalog.add(new Animal("Zebra", "Mammal", 5));
        animalCatalog.add(new Animal("Aardvark", "Mammal", 3));
        animalCatalog.add(new Animal("Falcon", "Bird", 2));
        animalCatalog.add(new Animal("Baboon", "Mammal", 4));

        Registry registry = new Registry(animalCatalog);

        System.out.println("=== 1. Catalog Display (Insertion Order) ===");
        animalCatalog.display();
        System.out.println("\n=== 2. Natural Sorting (Alphabetical by Name) ===");
        registry.sortNatural().forEach(System.out::println);
        System.out.println("\n=== 3. Alternate Sorting (Dynamic Sort by Age) ===");
        registry.orderBy("Age").forEach(System.out::println);
        System.out.println("\n=== 4. Categorized Grouping by Species ===");
        registry.groupBy("Species").forEach((species, list) -> {
            System.out.println("Species Group: " + species);
            list.forEach(animal -> System.out.println("    - " + animal));
        });
    }
}

Animals.main(null);
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

C. OCS data set: Student (original)

Kept unchanged so the original output stays reproducible next to your FantasyStudent output. Notice: isaac Newton sorts last because String.compareTo is case-sensitive. You fix that in Hack 2.

Code Runner Challenge

Run the ORIGINAL Pathway/Standing/Student demo and study where 'isaac Newton' lands in the natural sort.

View IPYNB Source
// CODE_RUNNER: Run the ORIGINAL Pathway/Standing/Student demo and study where 'isaac Newton' lands in the natural sort.
import java.util.*;

/** Pathway: a curriculum track (year 0-3). Natural order = by year. */
class Pathway extends AbstractCollectable {
    private int year;
    private String projectCapability;
    private String scrumTeamCapability;
    private String problemSolving;
    private String initiative;

    public Pathway(int year, String projectCapability, String scrumTeamCapability, String problemSolving, String initiative) {
        this.year = year;
        this.projectCapability = projectCapability;
        this.scrumTeamCapability = scrumTeamCapability;
        this.problemSolving = problemSolving;
        this.initiative = initiative;
    }

    public int getYear() { return year; }
    public String getProjectCapability() { return projectCapability; }

    @Override
    public Map<String, Object> getMap() {
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("Year", year);
        map.put("Project Capability", projectCapability);
        map.put("Scrum Team", scrumTeamCapability);
        map.put("Problem Solving", problemSolving);
        map.put("Initiative", initiative);
        return map;
    }

    @Override public int compareTo(Collectable o) { return Integer.compare(this.year, (Integer) o.getValue("Year")); }

    public static Catalog<Pathway> catalog() {
        Catalog<Pathway> catalog = new Catalog<>();
        catalog.add(new Pathway(0, "Fundamental", "Solo", "Remembering", "Passive"));
        catalog.add(new Pathway(1, "Project", "Pair (2-3)", "Analyzing", "Self-starter"));
        catalog.add(new Pathway(2, "System", "Group (~6)", "Applying", "Shaper"));
        catalog.add(new Pathway(3, "Research", "Team (~10)", "Creating", "Driver"));
        return catalog;
    }
}

/** Standing: academic level with a numeric weight so levels can be compared. */
class Standing extends AbstractCollectable {
    private String label;
    private int weight;

    public Standing(String label, int weight) { this.label = label; this.weight = weight; }

    public String getLabel() { return label; }
    public int getWeight() { return weight; }

    @Override
    public Map<String, Object> getMap() {
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("Label", label);
        map.put("Weight", weight);
        return map;
    }

    @Override public int compareTo(Collectable o) { return Integer.compare(this.weight, (Integer) o.getValue("Weight")); }

    public static Catalog<Standing> catalog() {
        Catalog<Standing> catalog = new Catalog<>();
        catalog.add(new Standing("Freshman", 0));
        catalog.add(new Standing("Sophomore", 1));
        catalog.add(new Standing("Junior", 2));
        catalog.add(new Standing("Senior", 3));
        return catalog;
    }
}

/** Student (original): composes Standing + Pathway and flattens them into one map. */
class Student extends AbstractCollectable {
    private String name;
    private Standing standing;
    private Pathway pathway;
    private String archetype;

    public Student(String name, Standing standing, Pathway pathway, String archetype) {
        this.name = name;
        this.standing = standing;
        this.pathway = pathway;
        this.archetype = archetype;
    }

    public String getName() { return name; }
    public Standing getStanding() { return standing; }
    public Pathway getPathway() { return pathway; }
    public String getArchetype() { return archetype; }

    @Override
    public Map<String, Object> getMap() {
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("Name", name);
        map.put("Standing", standing.getLabel());
        map.put("Standing Weight", standing.getWeight());
        map.put("Pathway Year", pathway.getYear());
        map.put("Technical Capability", pathway.getProjectCapability());
        map.put("Archetype", archetype);
        return map;
    }

    @Override
    public int compareTo(Collectable o) {
        return this.name.compareTo((String) o.getValue("Name"));
    }
}

/** Students: the ORIGINAL driver. Kept so we can compare old output vs FantasyStudent output. */
class Students {
    public static Catalog<Student> catalog() {
        Catalog<Pathway> pathwayCatalog = Pathway.catalog();
        Catalog<Standing> standingCatalog = Standing.catalog();
        Catalog<Student> studentCatalog = new Catalog<>();
        studentCatalog.add(new Student("Thomas Edison", standingCatalog.getItems().get(3), pathwayCatalog.getItems().get(3), "Scrummer"));
        studentCatalog.add(new Student("Nikola Tesla", standingCatalog.getItems().get(3), pathwayCatalog.getItems().get(2), "Technologist"));
        studentCatalog.add(new Student("Grace Hopper", standingCatalog.getItems().get(2), pathwayCatalog.getItems().get(3), "Planner"));
        studentCatalog.add(new Student("Marie Curie", standingCatalog.getItems().get(2), pathwayCatalog.getItems().get(1), "Analyst"));
        studentCatalog.add(new Student("Alan Turing", standingCatalog.getItems().get(2), pathwayCatalog.getItems().get(1), "Technologist"));
        studentCatalog.add(new Student("Hedy Lamarr", standingCatalog.getItems().get(2), pathwayCatalog.getItems().get(2), "Planner"));
        studentCatalog.add(new Student("isaac Newton", standingCatalog.getItems().get(1), pathwayCatalog.getItems().get(0), "Analyst"));
        studentCatalog.add(new Student("Bill Gates", standingCatalog.getItems().get(0), pathwayCatalog.getItems().get(0), "Analyst"));
        return studentCatalog;
    }

    public static void main(String[] args) {
        Catalog<Student> studentCatalog = catalog();
        Registry registry = new Registry(studentCatalog);

        System.out.println("=== 1. Student toString() & Catalog Display ===");
        studentCatalog.display();
        System.out.println("\n=== 2. Natural Sorting (compareTo by Name) ===");
        registry.sortNatural().forEach(System.out::println);
        System.out.println("\n=== 3. Alternate Sorting (by Pathway Year) ===");
        registry.orderBy("Pathway Year").forEach(System.out::println);
        System.out.println("\n=== 4. Grouping by Archetype ===");
        Map<Object, List<Collectable>> grouped = registry.groupBy("Archetype");
        grouped.forEach((key, value) -> {
            System.out.println("Archetype: " + key);
            value.forEach(item -> System.out.println("  - " + item));
        });
    }
}

Students.main(null);
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

Hacks & Practice Tasks

Submission template

Name: <your name>
Hack title (purpose): <what you built>
Runners saved: Popcorn, Hack 1, Hack 2, Hack 3, Hack 4
What I learned: <2-3 sentences>

Safety: submit only your own work, never paste passwords or tokens into a runner, and save every runner (πŸ’Ύ) first.

Popcorn Hack (5 min)

Predict first, run second. Each line should print true.

Code Runner Challenge

Popcorn Hack: add your own Animal, write your three predictions, then run. All lines should print true.

View IPYNB Source
// CODE_RUNNER: Popcorn Hack: add your own Animal, write your three predictions, then run. All lines should print true.
import java.util.*;

/**
 * Popcorn Hack: PREDICT before you run.
 * TODO 1: add ONE more Animal of your own where marked.
 * TODO 2: replace each ??? / -1 with your prediction BEFORE running. Each line should print true.
 */
public class AnimalsPopcorn {
    public static void main(String[] args) {
        Catalog<Animal> catalog = new Catalog<>();
        catalog.add(new Animal("Zebra", "Mammal", 5));
        catalog.add(new Animal("Aardvark", "Mammal", 3));
        catalog.add(new Animal("Falcon", "Bird", 2));
        catalog.add(new Animal("Baboon", "Mammal", 4));
        // TODO 1: catalog.add(new Animal("____", "____", __));
        Registry registry = new Registry(catalog);

        String firstByName = "???";           // first in natural order?
        String firstByAge = "???";            // first in orderBy("Age")?
        int numberOfSpeciesGroups = -1;       // groups made by groupBy("Species")?

        System.out.println(registry.sortNatural().get(0).getValue("Name").equals(firstByName));
        System.out.println(registry.orderBy("Age").get(0).getValue("Name").equals(firstByAge));
        System.out.println(registry.groupBy("Species").size() == numberOfSpeciesGroups);
    }
}

AnimalsPopcorn.main(null);
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

MCQ Check

1. Why does Registry take Catalog<? extends Collectable> instead of Catalog<Collectable>?

  • A. Wildcards are faster
  • B. A Catalog<Student> is not a Catalog<Collectable>, so the wildcard is needed to accept any subtype
  • C. Catalog is an interface
  • D. It blocks null values

2. What does registry.groupBy("Persona") return?

  • A. A sorted List<Collectable>
  • B. A Map<Object, List<Collectable>> from each persona to its members
  • C. A Set<String> of persona names
  • D. A new Catalog

3. orderBy("Pathway Year") keeps Marie Curie before Alan Turing (both Year 1). Why?

  • A. compareTo ranks names
  • B. List.sort is stable, so equal keys keep their original order
  • C. HashMap preserves order
  • D. Pathway Year is unique

My answers: 1 __ 2 __ 3 ___


Homework Hack

Finish each hack, then run the self-check at the end. Run the Tech Talk cells first.

Hack 1: Data-driven attribute class (15 min)

Create FantasyPersona (Speedster, Powerhouse, Mastermind, Visionary): extends AbstractCollectable; private name, superpower, powerLevel; getMap() keys Persona, Superpower, Power Level; compareTo() strongest first (tie-break by name); static catalog().

Code Runner Challenge

Hack 1: build FantasyPersona (fields, getMap, compareTo, catalog) and run main to display it.

View IPYNB Source
// CODE_RUNNER: Hack 1: build FantasyPersona (fields, getMap, compareTo, catalog) and run main to display it.
import java.util.*;

/**
 * TODO (Part 1): FantasyPersona models one survey "Fantasy Persona" option.
 * Requirements:
 *  - extends AbstractCollectable
 *  - private fields: name, superpower, powerLevel
 *  - getMap(): keys "Persona", "Superpower", "Power Level" (use LinkedHashMap)
 *  - compareTo(): natural order = powerLevel DESCENDING, then name (tie-breaker)
 *  - static catalog(): Speedster, Powerhouse, Mastermind, Visionary (pick your own power levels)
 */
public class FantasyPersona /* TODO: extends ... */ {
    // TODO: private fields

    // TODO: constructor

    // TODO: getters

    // TODO: @Override getMap()

    // TODO: @Override compareTo(Collectable o)

    // TODO: public static Catalog<FantasyPersona> catalog()

    public static void main(String[] args) {
        // TODO: display the catalog, then show the natural (sorted) order using a Registry
    }
}

FantasyPersona.main(null);
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

Hack 2: Compose it into FantasyStudent (15 min)

Create FantasyStudent holding a Standing, a Pathway and a FantasyPersona. getMap() returns Name, Standing, Standing Weight, Pathway Year, Technical Capability, then every persona attribute (hint: putAll). compareTo() sorts by name ignoring case.

// CODE_RUNNER: Hack 2: build FantasyStudent (composition, getMap with the persona's attributes, case-insensitive compareTo).
import java.util.*;

/**
 * TODO (Part 2): FantasyStudent = Student, but the String archetype is replaced by a FantasyPersona.
 * Requirements:
 *  - extends AbstractCollectable
 *  - fields: name, Standing standing, Pathway pathway, FantasyPersona persona
 *  - getMap(): Name, Standing, Standing Weight, Pathway Year, Technical Capability,
 *              then ALL persona attributes (hint: map.putAll(...))
 *  - compareTo(): alphabetical by Name, IGNORING CASE
 */
public class FantasyStudent /* TODO: extends ... */ {
    // TODO: fields, constructor, getters, getMap(), compareTo()
}

Hack 3: Registry and enhanced output (20 min)

Build FantasyStudents with at least 4 students (different personas, standings, pathways) and show natural sort, orderBy("Pathway Year"), orderBy("Standing Weight") and groupBy("Persona"). Compare with the original Students output.

Code Runner Challenge

Hack 3: build the roster and print natural sort, two orderBy views and groupBy Persona.

View IPYNB Source
// CODE_RUNNER: Hack 3: build the roster and print natural sort, two orderBy views and groupBy Persona.
import java.util.*;

/**
 * TODO (Part 3): driver for the enhanced output.
 * Requirements:
 *  - catalog(): build at least 4 FantasyStudents (different personas, standings, pathways)
 *  - main(): print these sections with headers:
 *      1. catalog display          2. natural sort
 *      3a. orderBy("Pathway Year") 3b. orderBy("Standing Weight")
 *      4. groupBy("Persona")
 */
public class FantasyStudents {
    public static Catalog<FantasyStudent> catalog() {
        // TODO: pull reference data from Pathway.catalog(), Standing.catalog(), FantasyPersona.catalog()
        return null;
    }

    public static void main(String[] args) {
        // TODO
    }
}

FantasyStudents.main(null);
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

Self-check for Hacks 1-3

Every line should say PASS.

Code Runner Challenge

Self-check: run after Hacks 1-3. Every line should say PASS.

View IPYNB Source
// CODE_RUNNER: Self-check: run after Hacks 1-3. Every line should say PASS.
import java.util.*;

/** One self-check for Hacks 1-3. Every line should say PASS. */
public class HackChecks {
    static void check(String label, boolean ok) { System.out.println((ok ? "PASS" : "FAIL") + " - " + label); }

    public static void main(String[] args) {
        // ---- Hack 1: FantasyPersona
        Catalog<FantasyPersona> personas = FantasyPersona.catalog();
        check("Hack 1: catalog has 4 personas", personas.getItems().size() == 4);
        check("Hack 1: extends AbstractCollectable", personas.getItems().get(0) instanceof AbstractCollectable);
        check("Hack 1: keys are Persona, Superpower, Power Level",
              personas.getItems().get(0).getKeys().equals(List.of("Persona", "Superpower", "Power Level")));
        List<Collectable> byPower = new Registry(personas).sortNatural();
        boolean desc = true;
        for (int i = 1; i < byPower.size(); i++)
            if ((Integer) byPower.get(i - 1).getValue("Power Level") < (Integer) byPower.get(i).getValue("Power Level")) desc = false;
        check("Hack 1: natural order is strongest first", desc);

        // ---- Hack 2: FantasyStudent
        FantasyPersona power = new FantasyPersona("Powerhouse", "Super strength", 9);
        FantasyStudent isaac = new FantasyStudent("isaac Newton", new Standing("Sophomore", 1),
                new Pathway(0, "Fundamental", "Solo", "Remembering", "Passive"), power);
        FantasyStudent marie = new FantasyStudent("Marie Curie", new Standing("Junior", 2),
                new Pathway(1, "Project", "Pair (2-3)", "Analyzing", "Self-starter"), power);
        check("Hack 2: persona attributes exposed in getMap", isaac.getValue("Persona").equals("Powerhouse")
              && isaac.getValue("Power Level").equals(9) && isaac.getKeys().contains("Superpower"));
        check("Hack 2: standing/pathway still exposed", isaac.getValue("Pathway Year").equals(0));
        check("Hack 2: compareTo ignores case (isaac before Marie)", isaac.compareTo(marie) < 0);

        // ---- Hack 3: FantasyStudents roster + registry
        Catalog<FantasyStudent> roster = FantasyStudents.catalog();
        Registry registry = new Registry(roster);
        check("Hack 3: at least 4 students", roster.getItems().size() >= 4);
        check("Hack 3: groupBy Persona makes 3+ groups", registry.groupBy("Persona").size() >= 3);
        check("Hack 3: orderBy moves the key to column 1",
              registry.orderBy("Pathway Year").get(0).getKeys().get(0).equals("Pathway Year"));
        List<Collectable> sorted = registry.sortNatural();
        boolean ok = true;
        for (int i = 1; i < sorted.size(); i++)
            if (((String) sorted.get(i - 1).getValue("Name")).compareToIgnoreCase((String) sorted.get(i).getValue("Name")) > 0) ok = false;
        check("Hack 3: natural sort is alphabetical", ok);
    }
}

HackChecks.main(null);
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

Hack 4: Boundary tests (15 min)

Write a check(label, condition) helper and test: empty catalog, one item, duplicates (stable order, one group), missing key (sortByField keeps order, groupBy makes a null group) and case-insensitive natural order. Finish with Summary: X passed, Y failed. Goal: 0 failed.

Code Runner Challenge

Hack 4: implement check(...) and test the five edge-case categories. Goal: 0 failed.

View IPYNB Source
// CODE_RUNNER: Hack 4: implement check(...) and test the five edge-case categories. Goal: 0 failed.
import java.util.*;

/**
 * TODO (Part 4): tiny test harness. Implement check(label, condition) that prints PASS/FAIL
 * and counts results, then test each category below (at least one meaningful check each):
 *   1. Empty catalog      -> sortNatural / sortByField / orderBy / groupBy all return empty, no exception
 *   2. Singleton          -> 1 item stays 1 item, exactly 1 group
 *   3. Duplicates         -> equal keys keep insertion order (stable sort); same group key -> one group
 *   4. Missing key        -> sortByField keeps order; groupBy puts everything under a null key
 *   5. Case-insensitive natural order (isaac Newton sorts between Hedy Lamarr and Marie Curie)
 * Finish by printing "Summary: X passed, Y failed".
 */
public class EdgeCases {
    // TODO: counters + check(...)

    public static void main(String[] args) {
        // TODO
    }
}

EdgeCases.main(null);
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

Reflection (2-3 sentences each)

  1. Why is Collectable an interface but AbstractCollectable an abstract class?
  2. What are the pros and cons of map.putAll(persona.getMap()) in FantasyStudent.getMap()?
  3. getValue(key) calls getMap() every time. What does that cost when sorting, and how could you reduce it?

My answers:

(write your answers here)

Grading plan (1.0 point)

| Item | Points | |β€”|β€”| | Popcorn Hack | 0.20 | | MCQ Check | 0.20 | | Hack 1: FantasyPersona | 0.15 | | Hack 2: FantasyStudent | 0.15 | | Hack 3: FantasyStudents output + self-check | 0.20 | | Hack 4: edge-case tests | 0.10 | | Total | 1.00 |

Quick validation: every runner executes; the self-check prints only PASS; EdgeCases prints 0 failed; original Students and new FantasyStudents output both visible; every runner saved (πŸ’Ύ).


LxD Cycle Process

Empathize: students must model data in Java before moving it through Spring (POJO to JPA to API). Define: CSA students need interfaces, abstract classes and inheritance to build reusable data types; goal: store, manage and present a collection by sorting, grouping and ordering. Ideate: how do we manage cognitive load using meaningful OCS examples? Prototype: a simple data set (Animal) plus an OCS data set (Student / FantasyStudent). Test: evidence comes from runner output, the self-checks, the MCQ, AI grading of submitted code, and the reflection.

References

  • Mortensen, J. (2026, Oct 18). Abstract Collectables & Dynamic Registries. Open Coding Society, CS112/CS113.
  • Oracle. Java SE 21 API documentation: Comparable, Comparator, Map.computeIfAbsent.

Submit Assignment

Use Submit Code on the OCS page to send your saved runners for automatic review, add optional notes, and check the submissions dashboard. (The button comes from the OCS page template.)

Notes (optional): ________

Submit Assignment

This submits the code from this lesson's code runner(s) below as a Gist for automatic review. Save each runner with its πŸ’Ύ button before submitting.

    Need to update a submission later? Open the submissions dashboard.