Thursday, November 11, 2021

Java Tutorial: Comparator Interface

Chapters

Comparator

Comparator is a comparison function, which imposes a total ordering on some collection of objects. Comparators can be passed to a sort method (such as Collections.sort or Arrays.sort) to allow precise control over the sort order. Comparators can also be used to control the order of certain data structures (such as sorted sets or sorted maps), or to provide an ordering for collections of objects that don't have a natural ordering.

The ordering imposed by a comparator c on a set of elements S is said to be consistent with equals if and only if c.compare(e1, e2)==0 has the same boolean value as e1.equals(e2) for every e1 and e2 in S.

Caution should be exercised when using a comparator capable of imposing an ordering inconsistent with equals to order a sorted set (or sorted map). Suppose a sorted set (or sorted map) with an explicit comparator c is used with elements (or keys) drawn from a set S. If the ordering imposed by c on S is inconsistent with equals, the sorted set (or sorted map) will behave "strangely." In particular the sorted set (or sorted map) will violate the general contract for set (or map), which is defined in terms of equals.

Unlike Comparable, a comparator may optionally permit comparison of null arguments. This interface is a member of the Java Collections Framework.

Implementing Comparator

To implement our own Comparator, we need to override the Comparator's compare() method.
import java.util.Comparator;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.io.IOException;

public class SampleClass{

  public static void main(String[] args)
                          throws IOException{
  
    ArrayList<File> files =
    new ArrayList<>();
    
    files.add(new File("F:\\name1\\eddie"));
    files.add(new File("C:\\name4\\ed"));
    files.add(new File("E:\\name3\\eddy"));
    files.add(new File("D:\\name2\\edd"));
    
    System.out.println("Default Sort");
    Collections.sort(files);
    
    for(File file : files)
      System.out.println(file.getCanonicalPath());
      
    System.out.println("Custom Sort");
    Collections.sort(files,new CustomSort());
    
    for(File file : files)
      System.out.println(file.getCanonicalPath());
  }
}

class CustomSort implements Comparator<File>{
  
  @Override
  public int compare(File o1, File o2){
    String o1_parent = o1.getParentFile().getName();
    String o2_parent = o2.getParentFile().getName();
    int result = 0;
    
    if(o1_parent != null && o2_parent != null)
      result = o1_parent.compareTo(o2_parent);
    else
      result = o1.getName().compareTo(o2.getName());
      
    return result;
  }
  
}

Result
Default Sort
C:\\name4\\ed
D:\\name2\\edd
E:\\name3\\eddy
F:\\name1\\eddie
Custom Sort
F:\\name1\\eddie
D:\\name2\\edd
E:\\name3\\eddy
C:\\name4\\ed
In the example above, the first sort is done by natural ordering the second sort is done by our custom sort. In the first sort, the names {ed, edd, eddy, eddie} are compared. In the second sort, the parent names {name1, name2, name3, name4} are compared.

If you're planning to create a sorting algorithm from scratch, you should read the compare() description in the documentation.

Comparator's Methods

Comparator has several methods that we can use. I'll demonstrate some of them here. If you wanna see all Comparator's method, go to the Comparator documentation. You need to be knowledgeable about generics and lambda expression to understand the syntax of the methods that I'm gonna explain in this chapter.

comparing() Method

This method accepts a function that extracts a Comparable sort key from a type T, and returns a Comparator<T> that compares by that sort key.

This method has two forms. In this topic, I'm gonna demonstrate this one:
comparing(Function<? super T,? extends U> keyExtractor)

The parameter here is a method reference with return type. The Comparable sort key will be extracted from the input and the output is a Comparator with sort key extracted from the input. Comparable sort key is a key that we can get from objects that implement Comparable. In this method, this key helps us sort an input from a method reference.
import java.util.Comparator;
import java.util.Collections;
import java.util.ArrayList;

public class SampleClass{

  public static void main(String[] args){
  
    ArrayList<Data> data =
    new ArrayList<>();
    
    System.out.println("Add elements...");
    data.add(new Data("John","Wells",24));
    data.add(new Data("Allen","Green",27));
    data.add(new Data("Grace","Lessy",20));
    
    Comparator<Data> comp =
    Comparator.comparing(Data::getLastName);
    
    System.out.println("Sort by last name...");
    Collections.sort(data, comp);
    for(Data dat : data)
      System.out.println(dat.getName());
    
    comp = Comparator.comparing(Data::getAge);
    Collections.sort(data, comp);
    System.out.println("Sort by age...");
    for(Data dat : data)
      System.out.println(dat.getName() + " | " +
                         dat.getAge());
  }
}

class Data{
  
  private String firstName;
  private String lastName;
  private Integer age;
  
  public Data(String firstName,
              String lastName,
              Integer age){
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
  }
  
  public String getFirstName(){
    return firstName;
  }
  
  public String getLastName(){
    return lastName;
  }
  
  public Integer getAge(){
    return age;
  }
  
  public String getName(){
    return firstName + " " + lastName;
  }
}

Result
Add elements...
Sort by last name...
Allen Green
Grace Lessy
John Wells
Sort by age...
Grace Lessy | 20
John Wells | 24
Allan Green | 27
nullsFirst() and nullsLast() Methods

nullsFirst() returns a null-friendly comparator that considers null to be less than non-null. nullsLast() returns a null-friendly comparator that considers null to be greater than non-null.

Method Forms:
nullsFirst(Comparator<? super T> comparator) nullsLast(Comparator<? super T> comparator)

This example demonstrates nullsFirst() and nullsLast().
import java.util.Comparator;
import java.util.Collections;
import java.util.ArrayList;

public class SampleClass{

  public static void main(String[] args){
  
    ArrayList<String> names =
    new ArrayList<>();
    
    System.out.println("Add elements...");
    names.add("John Wells");
    names.add("Allen Green");
    names.add(null);
    names.add("Grace Lessy");
    
    Comparator<String> comp =
    Comparator.nullsFirst(String::compareTo);
    
    System.out.println("Sort nulls first...");
    Collections.sort(names, comp);
    
    for(String str : names)
      System.out.println(str);
    
    comp = Comparator.nullsLast(String::compareTo);
    
    System.out.println("Sort nulls last...");
    Collections.sort(names, comp);
    
    for(String str : names)
      System.out.println(str);
    
  }
}

Result
Add elements...
Sort nulls first...
null
Allen Green
Grace Lessy
John Wells
Sort nulls last...
Allen Green
Grace Lessy
John Wells
null
thenComparing() Methods

The first form: thenComparing(Comparator<? super T> other)
Returns a lexicographic-order comparator with another comparator. If this(caller) Comparator considers two elements equal, i.e. compare(a, b) == 0, other(parameter) is used to determine the order. The returned comparator is serializable if the specified comparator is also serializable.
import java.util.Comparator;
import java.util.Collections;
import java.util.ArrayList;

public class SampleClass{

  public static void main(String[] args){
  
    ArrayList<String> names =
    new ArrayList<>();
    
    System.out.println("Add elements...");
    names.add("John Wells");
    names.add("allen Green");
    names.add("Allen Green");
    names.add(null);
    names.add("Grace Lessy");
    
    Comparator<String> comp =
    Comparator.nullsLast(String.CASE_INSENSITIVE_ORDER);
    
    Comparator<String> comp2 =
    comp.thenComparing(String::compareTo);
    
    System.out.println("Sort elements...");
    Collections.sort(names,comp2);
    
    for(String str : names)
      System.out.println(str);
  }
}

Result
Add elements...
Sort elements...
Allen Green
allen Green
Grace Lessy
John Wells
null
Take a look at the equal names. So first, we compare the names with String.CASE_INSENSITIVE_ORDER. Then, "Allen Green" and "allen Green" are equal when compared with insensitive order. Because two names are equal, the "other" comparator is used to compare these two equal names.

The "other" comparator copies that comparison pattern from compareTo() of String. compareTo() is case sensitive. Thus, "Allen Green" moved up and "allen Green" moved down because "a"'s numerical representation is greater than "A"'s.

No comments:

Post a Comment