ComparatorExamples.java

package fr.univtln.bruno.samples.java101.tp3.comparable;

import fr.univtln.bruno.samples.java101.tp3.Book;
import fr.univtln.bruno.samples.java101.tp3.Person;
import fr.univtln.bruno.samples.java101.tp3.functional.MappingAndSortingExamples;
import lombok.extern.slf4j.Slf4j;

import java.time.LocalDate;
import java.util.*;

/**
 * Examples illustrating Comparable/Comparator usage and common comparator idioms.
 *
 * <p>Note pédagogique : certains exemples utilisent volontairement des classes anonymes
 * au lieu de lambdas ou de method-references. L'objectif est didactique :
 * - montrer la structure complète d'un Comparator (méthode compare) pour les étudiants
 *   qui découvrent l'interface et la logique de comparaison ;
 * - permettre de discuter des différences entre les approches (verbosité vs concision).
 *
 * Chaque méthode comporte une alternative moderne (lambda / Comparator.comparing)
 * sous forme de commentaire pour que l'enseignant puisse basculer facilement vers la
 * version idiomatique une fois que les élèves ont compris les concepts.
 */
@Slf4j
public class ComparatorExamples {
  /**
   * Demonstrate natural ordering via Comparable implementation.
   */
  public static void naturalOrderingExample() {
    log.info("=== Natural Ordering ===");
    List<Person> people = new ArrayList<>(List.of(
      Person.of("Charlie", "Brown", 35),
      Person.of("Alice", "Smith", 30),
      Person.of("Bob", "Jones", 25),
      Person.of("Alice", "Brown", 28)
    ));
    log.debug("Before natural sort: {}", people);
    Collections.sort(people);
    log.debug("After natural sort: {}", people);
    MappingAndSortingExamples.printFullNames(people);
  }

  /**
   * Show simple comparators based on attributes.
   */
  public static void customComparatorExample() {
    log.info("=== Custom Comparator ===");
    List<Person> people = new ArrayList<>(List.of(
      Person.of("Charlie", "Brown", 35),
      Person.of("Alice", "Smith", 30),
      Person.of("Bob", "Jones", 25)
    ));
    log.debug("Before age sort: {}", people);
    // Use an anonymous Comparator instead of a method reference
    // (didactic) - shows the full compare method implementation.
    people.sort(new Comparator<Person>() {
      @Override
      public int compare(Person a, Person b) {
        return Integer.compare(a.age(), b.age());
      }
    });
    log.debug("After age asc sort: {}", people);
    log.info("By age asc: {}", people.stream().map(p -> p.getFullName() + "(" + p.age() + ")").toList());

    // Modern, idiomatic equivalent (commented for reference):
    // people.sort(Comparator.comparingInt(Person::age));

    // reversed using an anonymous Comparator as well
    people.sort(new Comparator<Person>() {
      @Override
      public int compare(Person a, Person b) {
        return Integer.compare(a.age(), b.age());
      }
    }.reversed());
    log.debug("After age desc sort: {}", people);
    log.info("By age desc: {}", people.stream().map(p -> p.getFullName() + "(" + p.age() + ")").toList());

    // Modern reversed example (commented):
    // people.sort(Comparator.comparingInt(Person::age).reversed());

    // compare by firstName with anonymous Comparator (avoid method reference)
    people.sort(new Comparator<Person>() {
      @Override
      public int compare(Person a, Person b) {
        return a.firstName().compareTo(b.firstName());
      }
    });
    log.debug("After firstName sort: {}", people);
    log.info("By firstName: {}", people.stream().map(Person::getFullName).toList());

    // Modern equivalent (commented):
    // people.sort(Comparator.comparing(Person::firstName));
  }

  /**
   * Chaining comparators to implement multi-key sorting.
   */
  public static void comparatorChainingExample() {
    log.info("=== Comparator Chaining ===");
    List<Person> people = new ArrayList<>(List.of(
      Person.of("Alice", "Smith", 30),
      Person.of("Bob", "Smith", 25),
      Person.of("Alice", "Jones", 30),
      Person.of("Charlie", "Brown", 35)
    ));
    log.debug("Before chaining sort: {}", people);
    // Build chained comparator using anonymous classes to avoid method references
    // (didactic). This shows how each comparator contributes to the final ordering.
    Comparator<Person> lastNameCmp = new Comparator<>() {
      @Override
      public int compare(Person a, Person b) {
        return a.lastName().compareTo(b.lastName());
      }
    };
    Comparator<Person> firstNameCmp = new Comparator<>() {
      @Override
      public int compare(Person a, Person b) {
        return a.firstName().compareTo(b.firstName());
      }
    };
    Comparator<Person> ageCmp = new Comparator<>() {
      @Override
      public int compare(Person a, Person b) {
        return Integer.compare(a.age(), b.age());
      }
    };
    people.sort(lastNameCmp.thenComparing(firstNameCmp).thenComparing(ageCmp));
    log.debug("After chaining sort: {}", people);
    log.info("Chained: {}", people.stream().map(p -> p.getFullName() + "(" + p.age() + ")").toList());

    // Modern, concise alternative (commented):
    // people.sort(Comparator.comparing(Person::lastName)
    //                      .thenComparing(Person::firstName)
    //                      .thenComparingInt(Person::age));
  }

  /**
   * Show Comparator factory methods such as naturalOrder, reverseOrder, nullsFirst/Last.
   */
  public static void comparatorFactoryMethodsExample() {
    log.info("=== Comparator Factory Methods ===");
    List<String> words = new ArrayList<>(List.of("banana", "Apple", "cherry", "DATE"));
    List<String> natural = new ArrayList<>(words);
    natural.sort(Comparator.naturalOrder());
    List<String> reversed = new ArrayList<>(words);
    reversed.sort(Comparator.reverseOrder());
    List<String> ci = new ArrayList<>(words);
    ci.sort(String.CASE_INSENSITIVE_ORDER);
    // sort by length using anonymous Comparator (avoid method reference)
    List<String> byLen = new ArrayList<>(words);
    byLen.sort(new Comparator<String>() {
      @Override
      public int compare(String a, String b) {
        return Integer.compare(a.length(), b.length());
      }
    });
    // sort by length then case-insensitive alphabetical order using anonymous comparator
    List<String> lenThenAlpha = new ArrayList<>(words);
    lenThenAlpha.sort(new Comparator<String>() {
      @Override
      public int compare(String a, String b) {
        int c = Integer.compare(a.length(), b.length());
        if (c != 0) return c;
        return String.CASE_INSENSITIVE_ORDER.compare(a, b);
      }
    });
    log.info("Natural {}", natural);
    log.info("Reverse {}", reversed);
    log.info("CaseInsensitive {}", ci);
    log.info("Length {}", byLen);
    log.info("Len+Alpha {}", lenThenAlpha);

    // Student note: avoid writing comparators that are not transitive. A non-transitive comparator
    // can produce A<B, B<C but A>C, which breaks sorting algorithms and collections relying on total order.
    // Example of a bad comparator (do NOT use in real code):
    // Comparator<String> bad = (a, b) -> {
    //   if (a.length() % 2 == 0 && b.length() % 2 == 1) return -1;
    //   if (a.length() % 2 == 1 && b.length() % 2 == 0) return 1;
    //   return a.compareTo(b);
    // };
  }

  /**
   * Null-handling comparators example.
   */
  public static void nullHandlingExample() {
    log.info("=== Null Handling Comparators ===");
    List<String> withNulls = new ArrayList<>(Arrays.asList("Charlie", null, "Alice", "Bob", null));
    List<String> nullsFirst = new ArrayList<>(withNulls);
    nullsFirst.sort(Comparator.nullsFirst(Comparator.naturalOrder()));
    List<String> nullsLast = new ArrayList<>(withNulls);
    nullsLast.sort(Comparator.nullsLast(Comparator.naturalOrder()));
    log.info("NullsFirst {}", nullsFirst);
    log.info("NullsLast {}", nullsLast);
  }

  /**
   * Complex sorting example combining multiple keys on Book objects.
   */
  public static void complexSortingExample() {
    log.info("=== Complex Sorting Books ===");
    List<Book> books = new ArrayList<>(List.of(
      Book.of("978-0134685991", "Effective Java", "Joshua Bloch", LocalDate.of(2018, 1, 6), 45.99),
      Book.of("978-0596009205", "Head First Java", "Kathy Sierra", LocalDate.of(2005, 2, 9), 39.99),
      Book.of("978-0134685992", "Java Concurrency", "Brian Goetz", LocalDate.of(2006, 5, 19), 42.99),
      Book.of("978-0321356680", "Clean Code", "Robert Martin", LocalDate.of(2008, 5, 28), 38.99),
      Book.of("978-0134685993", "Modern Java", "Joshua Bloch", LocalDate.of(2020, 3, 15), 49.99)
    ));
    log.debug("Books initial: {}", books);
    // Use anonymous comparators to avoid method references (didactic). Students can
    // compare this explicit form with Comparator.comparing and thenComparing.
    List<Book> byPrice = new ArrayList<>(books);
    byPrice.sort(new Comparator<Book>() {
      @Override
      public int compare(Book a, Book b) {
        return Double.compare(a.price(), b.price());
      }
    });

    List<Book> byDateDesc = new ArrayList<>(books);
    byDateDesc.sort(new Comparator<Book>() {
      @Override
      public int compare(Book a, Book b) {
        return a.publishedDate().compareTo(b.publishedDate());
      }
    }.reversed());

    List<Book> byAuthorPrice = new ArrayList<>(books);
    byAuthorPrice.sort(new Comparator<Book>() {
      @Override
      public int compare(Book a, Book b) {
        int c = a.author().compareTo(b.author());
        if (c != 0) return c;
        return Double.compare(a.price(), b.price());
      }
    });
    log.debug("byPrice: {} byDateDesc: {} byAuthorPrice: {}", byPrice, byDateDesc, byAuthorPrice);
    MappingAndSortingExamples.printBookDescriptions(byPrice);
    MappingAndSortingExamples.printBookDescriptions(byDateDesc);
    MappingAndSortingExamples.printBookDescriptions(byAuthorPrice);

    // Modern equivalents (commented):
    // byPrice.sort(Comparator.comparingDouble(Book::price));
    // byDateDesc.sort(Comparator.comparing(Book::publishedDate).reversed());
    // byAuthorPrice.sort(Comparator.comparing(Book::author).thenComparingDouble(Book::price));
  }

  /**
   * Min/Max examples using Comparators.
   */
  public static void minMaxExample() {
    log.info("=== Min/Max Example ===");
    List<Person> people = List.of(
      Person.of("Charlie", "Brown", 35),
      Person.of("Alice", "Smith", 30),
      Person.of("Bob", "Jones", 25)
    );
    // use anonymous comparator for age
    Comparator<Person> ageComparator = new Comparator<>() {
      @Override
      public int compare(Person a, Person b) {
        return Integer.compare(a.age(), b.age());
      }
    };
    Person youngest = Collections.min(people, ageComparator);
    Person oldest = Collections.max(people, ageComparator);
    log.info("Youngest {}({}) Oldest {}({})", youngest.getFullName(), youngest.age(), oldest.getFullName(), oldest.age());
    MappingAndSortingExamples.showMinMaxByAge(people);
  }

  /**
   * Examples of custom comparator implementations (anonymous, lambda, method reference).
   */
  public static void customComparatorImplementationExample() {
    log.info("=== Custom Implementation ===");
    Comparator<Person> anonymous = new Comparator<>() {
      public int compare(Person a, Person b) {
        return Integer.compare(a.age(), b.age());
      }
    };
    Comparator<Person> lambda = (a, b) -> Integer.compare(a.age(), b.age());
    // replace method reference comparator with anonymous comparator
    Comparator<Person> methodRef = new Comparator<>() {
      @Override
      public int compare(Person a, Person b) {
        return Integer.compare(a.age(), b.age());
      }
    };
    List<Person> people = new ArrayList<>(List.of(Person.of("Charlie", "Brown", 35), Person.of("Alice", "Smith", 30), Person.of("Bob", "Jones", 25)));
    people.sort(methodRef);
    MappingAndSortingExamples.printNamesWithAges(people);
    people.sort(anonymous.reversed());
    MappingAndSortingExamples.printNamesWithAges(people);
  }

  /**
   * Runner for comparator examples.
   *
   * @param args ignored
   */
  public static void main(String[] args) {
    naturalOrderingExample();
    customComparatorExample();
    comparatorChainingExample();
    comparatorFactoryMethodsExample();
    nullHandlingExample();
    complexSortingExample();
    minMaxExample();
    customComparatorImplementationExample();
  }
}