Collectables Homework

This homework follows the Abstract Collectables lesson and uses its Student, Catalog, and Registry classes.

  • Part A: removeStanding and sortedNames on an ArrayList<Student>
  • Part B: write a Course class that extends AbstractCollectable
  • Part C: countBy, a generic Registry helper

Do not use Collections.sort, List.sort, or streams in Part A. Each part has a code runner: write your code at the top, press Run, and keep going until every check prints PASS. The runner saves your code in this browser; use Copy Code to keep a copy.

Base Architecture (from the lesson)

These cells are copied unchanged from the lesson for reference. Each runner below already includes the classes it needs.

import java.util.Map;
import java.util.List;

/**
 * Collectable Interface
 * 
 * Defines a standardized data-access and comparison contract for multiple domain entities. 
 * 
 * Key Instructional Concepts:
 * - Polymorphism: Allows different data classes to be treated uniformly through this interface.
 * - Natural Ordering: Extends Comparable to enforce a default sorting mechanism.
 * - Map Projection: Exposes object fields as key-value pairs for dynamic inspection.
 * 
 * @author John Mortensen
 * @version 2.0
 */
public interface Collectable extends Comparable<Collectable> {

    /**
     * Requires the object's internal fields to be projected into a key-value map representation.
     * 
     * @return A Map containing field names as keys and their respective values.
     */
    Map<String, Object> getMap();

    /**
     * Retrieves all available field keys for this collectable entity.
     * 
     * @return A List of String keys representing the entity's attributes.
     */
    List<String> getKeys();

    /**
     * Fetches the value of a specific field dynamically by its key string.
     * 
     * @param key The attribute key to look up.
     * @return The Object value associated with the given key.
     */
    Object getValue(String key);
}
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * AbstractCollectable Base Class
 * 
 * Provides shared default implementations for common Collectable behaviors 
 * (like toString, getKeys, and getValue) derived entirely from getMap().
 * 
 * Key Instructional Concepts:
 * - Abstract Classes vs Interfaces: Using abstract classes to share code implementation while 
 *   enforcing contract implementation via interfaces.
 * - Template Method Pattern: Subclasses only need to supply getMap() and compareTo().
 */
public 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();
    }
}
import java.util.*;

/**
 * Catalog Class
 * 
 * Serves as a generic container for managing a collection of Collectable items.
 * 
 * Key Instructional Concepts:
 * - Generics: Uses <T extends Collectable> to ensure type safety while allowing flexibility.
 * - Encapsulation: Manages an internal list of items, providing controlled access via methods.
 * - Collection Management: Demonstrates basic CRUD-like operations (add, retrieve, display).
 * 
 * @author John Mortensen
 * @version 2.0
 */
public class Catalog<T extends Collectable> {
    private List<T> items = new ArrayList<>();

    /**
     * Adds a new item to the catalog.
     * 
     * @param item The Collectable item to be added.
     */
    public void add(T item) { items.add(item); }

    /**
     * Retrieves the full list of items currently in the catalog.
     * 
     * @return A List containing all stored items.
     */
    public List<T> getItems() { return items; }

    /**
     * Iterates through the items and prints them to the standard output.
     */
    public void display() {
        for (T item : items) {
            System.out.println(item);
        }
    }
}
import java.util.*;

/**
 * Registry Class
 * 
 * Provides advanced processing, sorting, and classification services across mixed or 
 * uniform Collectable catalogs.
 * 
 * Key Instructional Concepts:
 * - Wildcard Generics with Upper Bounds (? extends Collectable): Allows the Registry to accept 
 *   catalogs of any subtype (e.g., Catalog<Student> or Catalog<Animal>).
 * - Dynamic Field Sorting: Uses Java Comparators to sort items on-the-fly based on string-keyed 
 *   attributes rather than fixed properties.
 * - Categorized Grouping: Uses hash-based map aggregation (Map.computeIfAbsent) to partition 
 *   items into sub-lists by attribute.
 * 
 * @author John Mortensen
 * @version 2.0
 */
public class Registry {
    private List<Collectable> items;

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

    /**
     * A decorator that reorders the map projection so the primaryKey appears first.
     */
    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); }

        /**
         * Overriding the record's default toString to use the reordered map.
         */
        @Override 
        public String toString() {
            return getMap().toString();
        }
    }

    // Objective: Natural Sorting (using compareTo)
    public List<Collectable> sortNatural() {
        List<Collectable> sorted = new ArrayList<>(items);
        Collections.sort(sorted);
        return sorted;
    }

    // Objective: Alternate Sorting (dynamic field sorting)
    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;
    }

    /**
     * Sorts the collection by the specified key and returns a projected view 
     * where the sort key is moved to the first column.
     */
    public List<ProjectedCollectable> orderBy(String key) {
        return sortByField(key).stream()
                .map(item -> new ProjectedCollectable(item, key))
                .toList();
    }

    // Objective: Grouping by attribute key
    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;
    }
}
import java.util.*;

/**
 * Pathway Class
 * 
 * Represents a specific curriculum track or learning journey.
 * 
 * Key Instructional Concepts:
 * - Domain Modeling: Translates real-world curriculum attributes into a structured object.
 * - Data Projection: Implements `getMap()` to allow dynamic access to its fields.
 * - Natural Ordering: Implements `Comparable` to sort pathways by year.
 * 
 * @author John Mortensen
 * @version 2.0
 */
public 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")); }

    /**
     * Returns a pre-populated catalog of curriculum pathways.
     * 
     * @return A Catalog containing the four standard pathway years (0-3).
     */
    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;
    }
}
import java.util.*;

/**
 * Standing Class
 * 
 * Represents an academic or professional status level.
 * 
 * Key Instructional Concepts:
 * - Attribute Weighting: Uses a numeric weight to facilitate mathematical comparisons.
 * - Data Projection: Implements `getMap()` for dynamic attribute inspection.
 * - Natural Ordering: Implements `Comparable` to sort by weight.
 * 
 * @author John Mortensen
 * @version 2.0
 */
public 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")); }

    /**
     * Returns a pre-populated catalog of academic standings.
     * 
     * @return A Catalog containing Freshman, Sophomore, Junior, and Senior standings.
     */
    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;
    }
}
import java.util.*;

/**
 * Student Class
 * 
 * A complex domain entity that aggregates multiple `Collectable` components.
 * 
 * Key Instructional Concepts:
 * - Composition: Demonstrates how a class can be composed of other complex objects (`Standing`, `Pathway`).
 * - Dependency Management: Shows how a parent entity relies on the state of its child components.
 * - Polymorphic Comparison: Implements `compareTo` to provide a natural sort order based on the student's name.
 * - Map Projection: Flattens hierarchical data into a single-level map for easy registry access.
 * 
 * @author John Mortensen
 * @version 2.0
 */
public 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) {
        // Natural sorting by Name alphabetically
        return this.name.compareTo((String) o.getValue("Name"));
    }
}
import java.util.*;

/**
 * Students Driver Class
 * 
 * Serves as the main entry point for the demonstration, orchestrating the interaction 
 * between Catalogs, Registries, and domain entities.
 * 
 * Key Instructional Concepts:
 * - System Orchestration: Demonstrates how to initialize reference data and primary rosters.
 * - Registry Usage: Shows how a `Registry` can be used to perform complex operations on a `Catalog`.
 * - Data-Driven Testing: Uses a series of print statements to verify sorting, grouping, and display logic.
 * 
 * @author John Mortensen
 * @version 2.0
 */
public class Students {
    public static Catalog<Student> catalog() {
        // Master Catalog for Reference Data (Pathways & Standings)
        Catalog<Pathway> pathwayCatalog = Pathway.catalog();
        Catalog<Standing> standingCatalog = Standing.catalog(); 
        // Student Roster 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) {
        // Initialize the student catalog
        Catalog<Student> studentCatalog = catalog();
        // Initialize the registry with the student catalog
        Registry registry = new Registry(studentCatalog);

        // View the student catalog
        System.out.println("=== 1. Student toString() & Catalog Display ===");
        studentCatalog.display();
        // Sort and View the registry naturally (by Name)
        System.out.println("\n=== 2. Natural Sorting (compareTo by Name) ===");
        registry.sortNatural().forEach(System.out::println);
        // Sort and View the registry by an alternate field (Pathway Year)
        System.out.println("\n=== 3. Alternate Sorting (by Pathway Year) ===");
        registry.orderBy("Pathway Year").forEach(System.out::println);

        // Group the registry by Archetype using groupBy functionality
        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);

Animal (from the lesson)

Used by the Part C tests (the runner has its own copy).

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * Animal Class
 * 
 * Represents a concrete domain entity implementing the Collectable contract. 
 * This class models simple animal records with a name, species, and age.
 * 
 * Key Instructional Concepts:
 * - Interface Implementation: Fulfills the Collectable contract for uniform processing.
 * - Natural Ordering: Implements compareTo() to define default sorting behavior (alphabetical by name).
 * - Data Projection: Implements getMap() to expose object attributes dynamically for registry mapping.
 * 
 * @author John Mortensen
 * @version 2.0
 */
public class Animal extends AbstractCollectable {
    
    private String name;
    private String species;
    private int age;

    /**
     * Constructs a new Animal record.
     * 
     * @param name The name of the animal.
     * @param species The biological species or category.
     * @param age The age of the animal in years.
     */
    public Animal(String name, String species, int age) {
        this.name = name;
        this.species = species;
        this.age = age;
    }

    public String getName() { return name; }
    public String getSpecies() { return species; }
    public int getAge() { return age; }

    /**
     * Projects internal fields into a LinkedHashMap to maintain insertion order 
     * when inspected dynamically by registries or export tools.
     */
    @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;
    }

    /**
     * Defines natural ordering (Comparable interface contract).
     * Compares animals alphabetically by their name field.
     */
    @Override
    public int compareTo(Collectable o) {
        // Safe extraction and comparison of the "Name" attribute from another Collectable entity
        String otherName = (String) o.getValue("Name");
        return this.name.compareTo(otherName);
    }

}

Part A: Data Analysis with ArrayList

RosterAnalysis holds an ArrayList<Student>, the same setup as AP Question 3. Write both methods in the RosterAnalysis class at the top of the runner without changing their headers. The examples use Students.catalog().getItems(), in this order: Thomas Edison (Senior), Nikola Tesla (Senior), Grace Hopper (Junior), Marie Curie (Junior), Alan Turing (Junior), Hedy Lamarr (Junior), isaac Newton (Sophomore), Bill Gates (Freshman).

HW 1: removeStanding

Remove every student whose standing label equals label, and return the removed students in their original order. The students who stay keep their order. If nobody matches, return an empty list.

Example: removeStanding("Junior") returns [Grace Hopper, Marie Curie, Alan Turing, Hedy Lamarr] and leaves [Thomas Edison, Nikola Tesla, isaac Newton, Bill Gates].

A forward loop that removes without adjusting the index skips elements. Removing inside an enhanced for loop throws a ConcurrentModificationException.

HW 2: sortedNames

Return a new ArrayList<String> of all names in the order Student.compareTo gives. Write insertion sort or selection sort yourself, and do not change roster.

Example: [Alan Turing, Bill Gates, Grace Hopper, Hedy Lamarr, Marie Curie, Nikola Tesla, Thomas Edison, isaac Newton]

Code Runner Challenge

Write removeStanding and sortedNames, then run until all 5 checks PASS

View IPYNB Source
// CODE_RUNNER: Write removeStanding and sortedNames, then run until all 5 checks PASS
import java.util.*;
import java.util.function.Supplier;

// ---- Your code: write both methods without changing their headers ----
class RosterAnalysis {
    private ArrayList<Student> roster;

    RosterAnalysis(List<Student> students) {
        roster = new ArrayList<>(students);
    }

    public ArrayList<Student> getRoster() { return roster; }

    /** HW 1: Removes and returns all students whose standing label equals label. */
    public ArrayList<Student> removeStanding(String label) {
        /* to be implemented */
        return null;
    }

    /** HW 2: Returns all names in natural (compareTo) order without modifying roster. */
    public ArrayList<String> sortedNames() {
        /* to be implemented */
        return null;
    }
}

// ---- Tests: run after every change ----
public class PartATests {
    public static void main(String[] args) {
        Check.that("HW1 removes Juniors in order", () -> {
            RosterAnalysis ra = new RosterAnalysis(Students.catalog().getItems());
            return Check.names(ra.removeStanding("Junior")).equals(List.of("Grace Hopper", "Marie Curie", "Alan Turing", "Hedy Lamarr"));
        });
        Check.that("HW1 roster keeps the others in order", () -> {
            RosterAnalysis ra = new RosterAnalysis(Students.catalog().getItems());
            ra.removeStanding("Junior");
            return Check.names(ra.getRoster()).equals(List.of("Thomas Edison", "Nikola Tesla", "isaac Newton", "Bill Gates"));
        });
        Check.that("HW1 no match returns empty and changes nothing", () -> {
            RosterAnalysis ra = new RosterAnalysis(Students.catalog().getItems());
            return ra.removeStanding("Graduate").isEmpty() && ra.getRoster().size() == 8;
        });
        Check.that("HW2 names in compareTo order", () ->
            new RosterAnalysis(Students.catalog().getItems()).sortedNames().equals(List.of(
                "Alan Turing", "Bill Gates", "Grace Hopper", "Hedy Lamarr", "Marie Curie", "Nikola Tesla", "Thomas Edison", "isaac Newton")));
        Check.that("HW2 roster is not modified", () -> {
            RosterAnalysis ra = new RosterAnalysis(Students.catalog().getItems());
            ra.sortedNames();
            return ra.getRoster().get(0).getName().equals("Thomas Edison");
        });
        Check.summary();
    }
}

// ---- Lesson classes (do not edit): a compact copy so this runner compiles on its own ----
interface Collectable extends Comparable<Collectable> {
    Map<String, Object> getMap();
    List<String> getKeys();
    Object getValue(String key);
}

abstract class AbstractCollectable implements Collectable {
    public List<String> getKeys() { return new ArrayList<>(getMap().keySet()); }
    public Object getValue(String key) { return getMap().get(key); }
    public String toString() { return getMap().toString(); }
}

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; }
}

class Check {
    private static int passed = 0;
    private static int total = 0;

    // An exception counts as a FAIL so one unfinished method does not stop the other checks.
    static void that(String label, Supplier<Boolean> test) {
        total++;
        try {
            if (test.get()) { passed++; System.out.println("PASS  " + label); }
            else System.out.println("FAIL  " + label);
        } catch (Exception e) {
            System.out.println("FAIL  " + label + "  (" + e + ")");
        }
    }

    static void summary() { System.out.println("---- " + passed + " / " + total + " passed ----"); }

    static List<Object> names(List<? extends Collectable> items) {
        List<Object> names = new ArrayList<>();
        for (Collectable item : items) names.add(item.getValue("Name"));
        return names;
    }
}

class Standing extends AbstractCollectable {
    private String label;
    private int weight;
    Standing(String label, int weight) { this.label = label; this.weight = weight; }
    public String getLabel() { return label; }
    public int getWeight() { return weight; }
    public Map<String, Object> getMap() {
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("Label", label);
        map.put("Weight", weight);
        return map;
    }
    public int compareTo(Collectable o) { return Integer.compare(weight, (Integer) o.getValue("Weight")); }
}

class Pathway extends AbstractCollectable {
    private int year;
    private String projectCapability;
    Pathway(int year, String projectCapability) { this.year = year; this.projectCapability = projectCapability; }
    public int getYear() { return year; }
    public String getProjectCapability() { return projectCapability; }
    public Map<String, Object> getMap() {
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("Year", year);
        map.put("Project Capability", projectCapability);
        return map;
    }
    public int compareTo(Collectable o) { return Integer.compare(year, (Integer) o.getValue("Year")); }
}

class Student extends AbstractCollectable {
    private String name;
    private Standing standing;
    private Pathway pathway;
    private String archetype;
    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; }
    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;
    }
    public int compareTo(Collectable o) { return name.compareTo((String) o.getValue("Name")); }
}

class Students {
    static Catalog<Student> catalog() {
        Standing freshman = new Standing("Freshman", 0), sophomore = new Standing("Sophomore", 1);
        Standing junior = new Standing("Junior", 2), senior = new Standing("Senior", 3);
        Pathway p0 = new Pathway(0, "Fundamental"), p1 = new Pathway(1, "Project");
        Pathway p2 = new Pathway(2, "System"), p3 = new Pathway(3, "Research");
        Catalog<Student> catalog = new Catalog<>();
        catalog.add(new Student("Thomas Edison", senior, p3, "Scrummer"));
        catalog.add(new Student("Nikola Tesla", senior, p2, "Technologist"));
        catalog.add(new Student("Grace Hopper", junior, p3, "Planner"));
        catalog.add(new Student("Marie Curie", junior, p1, "Analyst"));
        catalog.add(new Student("Alan Turing", junior, p1, "Technologist"));
        catalog.add(new Student("Hedy Lamarr", junior, p2, "Planner"));
        catalog.add(new Student("isaac Newton", sophomore, p0, "Analyst"));
        catalog.add(new Student("Bill Gates", freshman, p0, "Analyst"));
        return catalog;
    }
}

PartATests.main(null);

Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

Part B: Class Design

HW 3: Course

Write a Course class that extends AbstractCollectable so this sequence gives these results:

Statement Result
Course csa = new Course("CSA", 2); Code CSA, 2 seats
csa.enroll("Ada") true
csa.enroll("Ada") false (already enrolled)
csa.enroll("Alan") true
csa.enroll("Grace") false (full)
csa.seatsLeft() 0
csa.toString() {Code=CSA, Capacity=2, Enrolled=2}

Natural order: fewer seats left comes first, and equal seats are ordered by code. Sorting CSA (0 left), CSP (5 left), and AI (0 left) gives AI, CSA, CSP.

Code Runner Challenge

Write the Course class, then run until all 8 checks PASS

View IPYNB Source
// CODE_RUNNER: Write the Course class, then run until all 8 checks PASS
import java.util.*;
import java.util.function.Supplier;

// ---- Your code: add instance variables and fill in every method ----
class Course extends AbstractCollectable {
    /* to be implemented: instance variables */

    Course(String code, int capacity) {
        /* to be implemented */
    }

    public boolean enroll(String studentName) {
        /* to be implemented */
        return false;
    }

    public int seatsLeft() {
        /* to be implemented */
        return -1;
    }

    @Override
    public Map<String, Object> getMap() {
        /* to be implemented */
        return new LinkedHashMap<>();
    }

    @Override
    public int compareTo(Collectable o) {
        /* to be implemented */
        return 0;
    }
}

// ---- Tests: run after every change ----
public class PartBTests {
    public static void main(String[] args) {
        Course csa = new Course("CSA", 2);
        Check.that("enroll Ada -> true", () -> csa.enroll("Ada"));
        Check.that("enroll Ada again -> false", () -> !csa.enroll("Ada"));
        Check.that("enroll Alan -> true", () -> csa.enroll("Alan"));
        Check.that("enroll Grace when full -> false", () -> !csa.enroll("Grace"));
        Check.that("seatsLeft -> 0", () -> csa.seatsLeft() == 0);
        Check.that("toString -> {Code=CSA, Capacity=2, Enrolled=2}", () -> csa.toString().equals("{Code=CSA, Capacity=2, Enrolled=2}"));
        Check.that("getValue(Enrolled) -> 2", () -> Integer.valueOf(2).equals(csa.getValue("Enrolled")));
        Check.that("sortNatural -> AI, CSA, CSP", () -> {
            Course ai = new Course("AI", 1);
            ai.enroll("Hedy");
            Catalog<Course> courses = new Catalog<>();
            courses.add(csa);
            courses.add(new Course("CSP", 5));
            courses.add(ai);
            List<Object> codes = new ArrayList<>();
            for (Collectable c : new Registry(courses).sortNatural()) codes.add(c.getValue("Code"));
            return codes.equals(List.of("AI", "CSA", "CSP"));
        });
        Check.summary();
    }
}

// ---- Lesson classes (do not edit): a compact copy so this runner compiles on its own ----
interface Collectable extends Comparable<Collectable> {
    Map<String, Object> getMap();
    List<String> getKeys();
    Object getValue(String key);
}

abstract class AbstractCollectable implements Collectable {
    public List<String> getKeys() { return new ArrayList<>(getMap().keySet()); }
    public Object getValue(String key) { return getMap().get(key); }
    public String toString() { return getMap().toString(); }
}

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; }
}

class Check {
    private static int passed = 0;
    private static int total = 0;

    // An exception counts as a FAIL so one unfinished method does not stop the other checks.
    static void that(String label, Supplier<Boolean> test) {
        total++;
        try {
            if (test.get()) { passed++; System.out.println("PASS  " + label); }
            else System.out.println("FAIL  " + label);
        } catch (Exception e) {
            System.out.println("FAIL  " + label + "  (" + e + ")");
        }
    }

    static void summary() { System.out.println("---- " + passed + " / " + total + " passed ----"); }

    static List<Object> names(List<? extends Collectable> items) {
        List<Object> names = new ArrayList<>();
        for (Collectable item : items) names.add(item.getValue("Name"));
        return names;
    }
}

class Registry {
    private List<Collectable> items;
    Registry(Catalog<? extends Collectable> catalog) { items = new ArrayList<>(catalog.getItems()); }
    public List<Collectable> sortNatural() {
        List<Collectable> sorted = new ArrayList<>(items);
        Collections.sort(sorted);
        return sorted;
    }
}

PartBTests.main(null);

Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

Part C: Registry Grouping

HW 4: countBy

Write a static method that works for any Collectable. It returns how many items have each value of key, with keys in first-seen order like groupBy. For the lesson roster, countBy(roster, "Archetype") gives {Scrummer=1, Technologist=2, Planner=2, Analyst=3}.

Code Runner Challenge

Write countBy, then run until all 3 checks PASS

View IPYNB Source
// CODE_RUNNER: Write countBy, then run until all 3 checks PASS
import java.util.*;
import java.util.function.Supplier;

// ---- Your code: write countBy without changing its header ----
class RegistryStats {
    /** HW 4: Counts items by the value of key, with keys in first-seen order. */
    public static Map<Object, Integer> countBy(List<? extends Collectable> items, String key) {
        /* to be implemented */
        return null;
    }
}

// ---- Tests: run after every change ----
public class PartCTests {
    public static void main(String[] args) {
        List<Student> roster = Students.catalog().getItems();
        Check.that("countBy Archetype in first-seen order", () ->
            RegistryStats.countBy(roster, "Archetype").toString().equals("{Scrummer=1, Technologist=2, Planner=2, Analyst=3}"));
        Check.that("countBy works on Animals too", () -> {
            Catalog<Animal> animals = new Catalog<>();
            animals.add(new Animal("Aardvark", "Mammal", 3));
            animals.add(new Animal("Falcon", "Bird", 2));
            animals.add(new Animal("Zebra", "Mammal", 5));
            return RegistryStats.countBy(animals.getItems(), "Species").toString().equals("{Mammal=2, Bird=1}");
        });
        Check.that("countBy on empty list is empty", () -> RegistryStats.countBy(new ArrayList<Student>(), "Archetype").isEmpty());
        Check.summary();
    }
}

// ---- Lesson classes (do not edit): a compact copy so this runner compiles on its own ----
interface Collectable extends Comparable<Collectable> {
    Map<String, Object> getMap();
    List<String> getKeys();
    Object getValue(String key);
}

abstract class AbstractCollectable implements Collectable {
    public List<String> getKeys() { return new ArrayList<>(getMap().keySet()); }
    public Object getValue(String key) { return getMap().get(key); }
    public String toString() { return getMap().toString(); }
}

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; }
}

class Check {
    private static int passed = 0;
    private static int total = 0;

    // An exception counts as a FAIL so one unfinished method does not stop the other checks.
    static void that(String label, Supplier<Boolean> test) {
        total++;
        try {
            if (test.get()) { passed++; System.out.println("PASS  " + label); }
            else System.out.println("FAIL  " + label);
        } catch (Exception e) {
            System.out.println("FAIL  " + label + "  (" + e + ")");
        }
    }

    static void summary() { System.out.println("---- " + passed + " / " + total + " passed ----"); }

    static List<Object> names(List<? extends Collectable> items) {
        List<Object> names = new ArrayList<>();
        for (Collectable item : items) names.add(item.getValue("Name"));
        return names;
    }
}

class Standing extends AbstractCollectable {
    private String label;
    private int weight;
    Standing(String label, int weight) { this.label = label; this.weight = weight; }
    public String getLabel() { return label; }
    public int getWeight() { return weight; }
    public Map<String, Object> getMap() {
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("Label", label);
        map.put("Weight", weight);
        return map;
    }
    public int compareTo(Collectable o) { return Integer.compare(weight, (Integer) o.getValue("Weight")); }
}

class Pathway extends AbstractCollectable {
    private int year;
    private String projectCapability;
    Pathway(int year, String projectCapability) { this.year = year; this.projectCapability = projectCapability; }
    public int getYear() { return year; }
    public String getProjectCapability() { return projectCapability; }
    public Map<String, Object> getMap() {
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("Year", year);
        map.put("Project Capability", projectCapability);
        return map;
    }
    public int compareTo(Collectable o) { return Integer.compare(year, (Integer) o.getValue("Year")); }
}

class Student extends AbstractCollectable {
    private String name;
    private Standing standing;
    private Pathway pathway;
    private String archetype;
    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; }
    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;
    }
    public int compareTo(Collectable o) { return name.compareTo((String) o.getValue("Name")); }
}

class Students {
    static Catalog<Student> catalog() {
        Standing freshman = new Standing("Freshman", 0), sophomore = new Standing("Sophomore", 1);
        Standing junior = new Standing("Junior", 2), senior = new Standing("Senior", 3);
        Pathway p0 = new Pathway(0, "Fundamental"), p1 = new Pathway(1, "Project");
        Pathway p2 = new Pathway(2, "System"), p3 = new Pathway(3, "Research");
        Catalog<Student> catalog = new Catalog<>();
        catalog.add(new Student("Thomas Edison", senior, p3, "Scrummer"));
        catalog.add(new Student("Nikola Tesla", senior, p2, "Technologist"));
        catalog.add(new Student("Grace Hopper", junior, p3, "Planner"));
        catalog.add(new Student("Marie Curie", junior, p1, "Analyst"));
        catalog.add(new Student("Alan Turing", junior, p1, "Technologist"));
        catalog.add(new Student("Hedy Lamarr", junior, p2, "Planner"));
        catalog.add(new Student("isaac Newton", sophomore, p0, "Analyst"));
        catalog.add(new Student("Bill Gates", freshman, p0, "Analyst"));
        return catalog;
    }
}

class Animal extends AbstractCollectable {
    private String name;
    private String species;
    private int age;
    Animal(String name, String species, int age) { this.name = name; this.species = species; this.age = age; }
    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;
    }
    public int compareTo(Collectable o) { return name.compareTo((String) o.getValue("Name")); }
}

PartCTests.main(null);

Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

Why These Homeworks

Each problem practices one main idea from the lesson, in the form the AP CSA exam tests it.

  • compareTo and sorting (HW 2): natural ordering is how sortNatural() works, and AP questions often hinge on compareTo results.
  • Removing from an ArrayList (HW 1): AP Question 3 is about ArrayList data, and 2021 Q3 (ClubMembers) asked for exactly this kind of removal.
  • Interfaces and abstract classes (HW 3): you write the parts of the contract a subclass must fill in. getKeys, getValue, and toString then come free from AbstractCollectable.
  • Grouping by key (HW 4): this builds on groupBy and works on any Collectable, which is the lesson’s main idea.

Sources: AP CSA exam format, past AP questions, 2021 Q3 walkthrough.

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.