Comparable Implementations

13 min read

Foundation for Comparision

The Collectable class by implenting Comparable builds foundations for comparing (compareTo) abstract objects.

/**
 * Sortable Objects: Representation-Based Ordering.
 *
 * Defines a shared comparison contract for typed objects.
 *
 * 1. CONTRACT: Subclasses supply a type, an active key, and a string representation.
 * 2. ORDERING: compareTo compares those representations lexicographically.
 * 3. OUTPUT: print sorts the supplied array in place and prints its contents.
 *
 * @see Animal
 */
public abstract class Collectable implements Comparable <Collectable> {
	public final String masterType = "Collectable";
	private String type;	// extender should define their data type

	/** Common contract for enum-based ordering keys. */
	public interface KeyTypes {
		String name();
	}
	protected abstract KeyTypes getKey();  	// this method helps force usage of KeyTypes

	// getter
	public String getMasterType() {
		return masterType;
	}

	// getter
	public String getType() {
		return type;
	}

	// setter
	public void setType(String type) {
		this.type = type;
	}
	
	// this method is used to establish key order
	public abstract String toString();

	// this method is used to compare toString of objects
	public int compareTo(Collectable obj) {
		return this.toString().compareTo(obj.toString());
	}

	// static print method used by extended classes
	public static void print(Collectable[] objs) {
		// print 'Object' properties
		System.out.println(objs.getClass() + " " + objs.length);

		// print 'Collectable' properties
		if (objs.length > 0) {
			Collectable obj = objs[0];	// Look at properties of 1st element
			System.out.println(
					obj.getMasterType() + ": " + 
					obj.getType() +
					" listed by " +
					obj.getKey());
		}

		// print "Collectable: Objects'
		Collections.sort(Arrays.asList(objs));
		for(Object o : objs)	// observe that type is Opaque
			System.out.println(o);

		System.out.println();
	}
}

Foundations for Building a catalog

A catalog is defined to contain an Entry of keys and values. The values are created in a Map form to provide flexibility in content. The catalog ensures a Set so that duplicates are not created.

Static examples are shown characterizing a Set of animal names and a Map of attributes.

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;

/**
 * Catalog: Keyed Properties with Entry Views.
 *
 * Separates property storage from the entries passed to consuming objects.
 *
 * 1. INDEX (HashMap): Provides average O(1) lookup and insertion by case-sensitive key.
 * 2. UNIQUENESS: For non-null values, duplicate adds return false without replacement.
 * 3. ENTRY VIEW: get returns the key with its properties, or null when absent.
 *    String representations retain both; iteration order is unspecified.
 *
 * @see Animal
 */
public class Catalog {
    /**
     * Catalog Entry: Key and Property Reference.
     *
     * Preserves the key when properties are passed to an Animal.
     * Returns {key, value} from toString; the property map is not copied.
     */
    public record Entry(String key, Map<String, Object> value) {
        /* string representation
         * returns this entry as {key, value}, without printing
         */
        @Override
        public String toString() {
            return "{" + key + ", " + value + "}";
        }
    }

    // unique keys; order is not guaranteed, the same rule as HashSet
    private final Map<String, Map<String, Object>> catalog = new HashMap<>();

    /* add refuses a duplicate key
     * returns true when the key was added, false when it was already present
     */
    public boolean add(String key, Map<String, Object> value) {
        // putIfAbsent leaves an existing key unchanged
        return catalog.putIfAbsent(key, value) == null;
    }

    /* getter
     * returns the key and properties, or null when the key is missing
     */
    public Entry get(String key) {
        Map<String, Object> value = catalog.get(key);
        return value == null ? null : new Entry(key, value);
    }

    /* Set contains
     * true when the key is already present
     */
    public boolean contains(String key) {
        return catalog.containsKey(key);
    }

    /* key set
     * Map keys are a Set: no duplicates, and order is not guaranteed
     */
    public Set<String> keys() {
        return Collections.unmodifiableSet(catalog.keySet());
    }

    /* string representation
     * returns each entry's string representation on its own line
     */
    @Override
    public String toString() {
        StringJoiner entries = new StringJoiner(System.lineSeparator());
        for (Map.Entry<String, Map<String, Object>> entry : catalog.entrySet()) {
            entries.add(new Entry(entry.getKey(), entry.getValue()).toString());
        }
        return entries.toString();
    }

    // Create and populate catalog for animal names and properties
    public static Catalog animalSet = new Catalog();
    static {
        animalSet.add("lion", Map.of("baby", "cub", "adult", 8));
        animalSet.add("pig", Map.of("baby", "piglet", "adult", 3));
        animalSet.add("robin", Map.of("baby", "chick", "adult", 1));
        animalSet.add("cat", Map.of("baby", "kitten", "adult", 2));
        animalSet.add("dog", Map.of("baby", "puppy", "adult", 2));
    }

    /* main to test Catalog Set properties
     * a duplicate add is refused
     */
    public static void main(String[] args) {
        for (String key : animalSet.keys()) {
            System.out.println(animalSet.get(key));
        }

        // A second add of the same key is refused; stored properties stay kitten/2
        boolean added = animalSet.add("cat", Map.of("baby", "duplicate", "adult", 99));
        System.out.println("Cat is Duplicate and adding it is " + added); // false
        System.out.println("Cat dataisstill" + animalSet.get("cat"));
        System.out.println("Checking forMissing data, return values is " + animalSet.get("missing")); // null
    }
}
Catalog.main(null);

{robin, {adult=1, baby=chick}}
{cat, {adult=2, baby=kitten}}
{dog, {adult=2, baby=puppy}}
{lion, {adult=8, baby=cub}}
{pig, {adult=3, baby=piglet}}
Cat is Duplicate and adding it is false
Cat dataisstill{cat, {adult=2, baby=kitten}}
Checking forMissing data, return values is null

Customized Collectable

The class Animal inherits and implements Collectable. It implements and overrides key methods to allow Collectable to remain generic and Animal to be customized. The toString method is critcal for Comparable functionality.

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/**
 * Animal: Catalog-Backed Data with Dynamic Ordering.
 *
 * Combines individual animal attributes with a catalog entry.
 *
 * 1. DATA: Stores surname, catalog entry, age, and color.
 * 2. REPRESENTATION: Shows species and life stage instead of raw catalog properties.
 *    Age >= the catalog's adult age yields adult; otherwise uses the baby name.
 * 3. OUTPUT: Moves the selected field first, keeping species and life stage together.
 *    Age is padded to at least two digits only when leading the output.
 * 4. ORDERING: A static key controls representation and comparison for all instances.
 *    Changing the key requires re-sorting any existing sorted view.
 *
 * @see Collectable
 * @see Catalog.Entry
 */
public class Animal extends Collectable {
	// Class data
	public static KeyTypes key = KeyType.defaultKey;  // static initializer
	public static void setOrder(KeyTypes key) { Animal.key = key; }
	/** Field priorities used by the shared ordering key. */
	public enum KeyType implements KeyTypes {defaultKey, surname, name, age, color}

	// Instance data
	private final String surname;
	private final Catalog.Entry name;
	private final int age;
	private final String color;

	/* constructor
	 *
	 */
	public Animal(String surname, Catalog.Entry name, int age, String color)
	{
		super.setType("Animal");
		this.surname = surname;
		this.name = Objects.requireNonNull(name, "Animal requires a catalog entry");
		this.age = age;
		this.color = color;
	}

	/* 'Collectable' requires getKey to help enforce KeyTypes usage */
	@Override
	protected KeyTypes getKey() { return Animal.key; }

	/* Getters / Accessors
	 * 
	 */
	public String getSurname() { return this.surname; }
	public Catalog.Entry getName() { return this.name; }
	public int getAge() { return this.age; }
	public String getColor() { return this.color; }

	
	/* Returns adult at or above the catalog threshold; otherwise the baby name. */
	public String getLifeStage() {
		Object adultAge = this.name.value().get("adult");
		if (!(adultAge instanceof Integer threshold)) {
			throw new IllegalStateException("Catalog entry " + this.name.key() + " requires an integer adult age");
		}
		if (this.age >= threshold) return "adult";
		Object babyName = this.name.value().get("baby");
		if (!(babyName instanceof String baby)) {
			throw new IllegalStateException("Catalog entry " + this.name.key() + " requires a baby name");
		}
		return baby;
	}

	/* Returns selected-field-first output for display and comparison.
	 * Species and life stage form one field; catalog entries remain unchanged.
	 */
	@Override
	public String toString()
	{
		// find sort order based oon selected key
		KeyTypes selectedKey = this.getKey();
		// build output with age conditons: adult or baby descriptor
		String speciesAndStage = this.name.key() + ", " + this.getLifeStage();
		String ageText = KeyType.age.equals(selectedKey)
			? String.format("%02d", this.age) : Integer.toString(this.age);
		// form the array of fields for output
		List<String> fields = new ArrayList<>(Arrays.asList(this.surname, speciesAndStage, this.color, ageText));
		// determin positions of candidate key fields
		int first = 0;
		if (KeyType.name.equals(selectedKey)) first = 1;
		else if (KeyType.color.equals(selectedKey)) first = 2;
		else if (KeyType.age.equals(selectedKey)) first = 3;
		// Move key element to first position and shift others accordingly
		if (first != 0) fields.add(0, fields.remove(first));
		//return super.getType() + ": " + String.join(", ", fields);
		return String.join(", ", fields);
	}

	// Test data initializer
	public static Animal[] animals() {
		// Assign catalog to Animal class
		return new Animal[]{
				new Animal("Simba", Catalog.animalSet.get("lion"), 6, "Gold"),
				new Animal("Porky", Catalog.animalSet.get("pig"), 3, "Pink"),
				new Animal("Tweety", Catalog.animalSet.get("robin"), 7, "Red"),
				new Animal("Hobbes", Catalog.animalSet.get("cat"), 10, "Black"),
				new Animal("Mittens", Catalog.animalSet.get("cat"), 1, "Calico"),
				new Animal("Duke", Catalog.animalSet.get("dog"), 14, "Brown")
		};
	}
	
	/* main to test Animal class
	 * 
	 */
	public static void main(String[] args)
	{
		// Use class method to get test data
		Animal[] objs = Animal.animals(); 

		// print surname order, default 
		Animal.setOrder(KeyType.defaultKey);
		Animal.print(objs);

		// print in name order
		Animal.setOrder(KeyType.name);
		Animal.print(objs);

		// print in age order
		Animal.setOrder(KeyType.age);
		Animal.print(objs);
	}

}
Animal.main(null);
class [LREPL.$JShell$19DN$Animal; 6
Collectable: Animal listed by defaultKey
Duke, dog, adult, Brown, 14
Hobbes, cat, adult, Black, 10
Mittens, cat, kitten, Calico, 1
Porky, pig, adult, Pink, 3
Simba, lion, cub, Gold, 6
Tweety, robin, adult, Red, 7

class [LREPL.$JShell$19DN$Animal; 6
Collectable: Animal listed by name
cat, adult, Hobbes, Black, 10
cat, kitten, Mittens, Calico, 1
dog, adult, Duke, Brown, 14
lion, cub, Simba, Gold, 6
pig, adult, Porky, Pink, 3
robin, adult, Tweety, Red, 7

class [LREPL.$JShell$19DN$Animal; 6
Collectable: Animal listed by age
01, Mittens, cat, kitten, Calico
03, Porky, pig, adult, Pink
06, Simba, lion, cub, Gold
07, Tweety, robin, adult, Red
10, Hobbes, cat, adult, Black
14, Duke, dog, adult, Brown

Map

Map-Based Indexing with Dynamic Views. This pattern optimizes for high-frequency writes by separating the primary storage (Index) from the sorted representation (View).

graph TD
    subgraph index ["Source of Truth (Index)"]
        A[HashMap] -->|"O(1) Access"| B("Data Objects")
    end

    subgraph view ["Presentation Layer (View)"]
        B -->|"On-Demand Conversion"| C["Array/List"]
        C -->|"O(n log n) Sort"| D("Sorted View")
    end
import java.util.Map;
import java.util.HashMap;

/**
 * The Dual-Structure Pattern: Map-Based Indexing with Dynamic Views.
 *
 * Separates keyed storage from on-demand sorted output.
 *
 * 1. INDEX (HashMap): Provides average O(1) lookup, insertion, and deletion.
 *    Adding an existing key replaces its value.
 * 2. VIEW (Array): Copies values in O(n), then sorts in O(n log n) when printed.
 *    Stored values must be Collectable instances; index order remains unchanged.
 *
 * @see Animal
 */
public class Registry {

    /** 
     * Primary index for average O(1) lookups.
     * Accepts Object values; sorted output requires Collectable instances.
     */
    private final Map<String, Object> index = new HashMap<>();

    /**
     * Adds an item to the registry.
     * 
     * @param key The unique identifier.
     * @param item The object to store.
     */
    public void add(String key, Object item) {
        index.put(key, item);
    }

    /**
     * Removes an item from the registry.
     * 
     * @param key The unique identifier.
     * @return The removed object, or null.
     */
    public Object remove(String key) {
        return index.remove(key);
    }

    /**
     * Retrieves an item from the registry.
     * 
     * @param key The unique identifier.
     * @return The object, or null.
     */
    public Object get(String key) {
        return index.get(key);
    }

    /**
     * Generates and prints a sorted view of the registry contents.
     */
    public void print() {
        // Convert Map values to an array for processing. $O(n)$ complexity.
        Collectable[] objs = index.values().toArray(new Collectable[0]);
        
        // Leverages polymorphism: Collectable.print handles the specific implementation.
        Collectable.print(objs);
    }

    /**
     * Demonstration of the Registry pattern using Animal objects.
     */
    public static void main(String[] args) {
        Registry pets = new Registry();
        Animal[] animals = Animal.animals();
        
        for (Animal animal : animals) {
            // $O(1)$ insertion via HashMap.
            pets.add(animal.getSurname(), animal);
        }
        
        // Default view (sorted by surname).
        Animal.setOrder(Animal.KeyType.defaultKey);
        pets.print();
        
        // Dynamically change sorting criteria.
        Animal.setOrder(Animal.KeyType.age);
        System.out.println("Sorted by age:");
        pets.print();
        
        // Demonstrate removal.
        String key = "Mittens";
        Object animal = pets.remove(key);
        
        // Runtime dispatch uses Animal.toString despite the Object reference.
        System.out.println("Removed: " + animal);
        pets.print();
    }
}
Registry.main(null);
class [LREPL.$JShell$18M$Collectable; 6
Collectable: Animal listed by defaultKey
Duke, dog, adult, Brown, 14
Hobbes, cat, adult, Black, 10
Mittens, cat, kitten, Calico, 1
Porky, pig, adult, Pink, 3
Simba, lion, cub, Gold, 6
Tweety, robin, adult, Red, 7

Sorted by age:
class [LREPL.$JShell$18M$Collectable; 6
Collectable: Animal listed by age
01, Mittens, cat, kitten, Calico
03, Porky, pig, adult, Pink
06, Simba, lion, cub, Gold
07, Tweety, robin, adult, Red
10, Hobbes, cat, adult, Black
14, Duke, dog, adult, Brown

Removed: 01, Mittens, cat, kitten, Calico
class [LREPL.$JShell$18M$Collectable; 5
Collectable: Animal listed by age
03, Porky, pig, adult, Pink
06, Simba, lion, cub, Gold
07, Tweety, robin, adult, Red
10, Hobbes, cat, adult, Black
14, Duke, dog, adult, Brown

Set

Below is an example using a java.util.Set and listed are some key things to consider when using this data structure. A Set works similarly to a key in a HashMap, a Set is just the Key.

  1. No duplicates: A Set does not allow duplicate elements. If you try to add an element that already exists in the Set, the add() method will return false and the Set will not be modified. Duplicate add is shown in example.

  2. Unordered: A Set does not maintain the insertion order of elements. The order of elements in a Set may change as elements are added or removed.

  3. Equality: Two Sets are considered equal if they have the same elements, regardless of their order. The equals() method is used to test for Set equality.

  4. Implementation classes: Java provides several implementation classes for the Set interface, including HashSet, LinkedHashSet, and TreeSet. Each implementation has different performance characteristics and is optimized for different use cases.

  5. Iterator: The iterator() method can be used to iterate over the elements in a Set. The order in which elements are returned by the iterator is not defined and may change over time as elements are added or removed from the Set. The forEach() method is another way to iterate over the elements in a Set, and it allows you to pass a lambda expression to process each element in the Set. Lambda expression is shown in example.

Hacks

Analyze the Big O complexity on Sorts.

  • Establish analytics including: time to sort, number of comparisons and number of swaps.
  • Average the results for each each Sort, run each at least 12 times and 5000 elements. You should throw out High and Low when doing analysis.
  • Make your final/judgement on best sort: Number of Comparisons, Number of Swaps, Big O complexity, and Total Time.

Build your own Hashmap. Make a HashMap to correspond to a Data Structure using a Collection.

  • Be sure to have 5000 records
  • Perform analysis on Binary Search vs HashMap Lookup, try using random to search and find 100 keys in 5000 records. Perform 12 times and throw out high and low.

Extra, Practical learning

  • Performing Iteration, Delete, and Add operations are another way to analyze Collection vs HashMap data structure.
  • A HashMap and a Collection can be used in a Class, POJO and API.
  • Make a Diagram on the Pros and Cons of Collection vs HashMap

Computer Science A