Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom Comparator

Given the following list: "A", "B", "C", "D", "E", "F", "G"

I need a comparator that does the following sorting:

  • specify a certain element (e.g. "D")
  • start with the element
  • followed by all following elements of the original list in the original order
  • followed by all preceding elements of the original list in the original order

The result would be: "D", "E", "F", "G", "A", "B", "C"


Please be aware that I know that I could just do stuff similar to the following:

List<String> following = myList.subList(myList.indexOf("D") + 1, myList.size());
List<String> preceding = myList.subList(0, myList.indexOf("D"));
List<String> newList = Stream.of(Collections.singletonList("D"), following, preceding)
                       .flatMap(List::stream)
                       .collect(Collectors.toList());

In this question I explicitly mean a Comparator implementation.


It is clear that it will have to have the list & element as a parameter, I am just not clear about the comparison algorithm itself:

private static class MyComparator<T> implements Comparator<T> {

  private final List<T> list;
  private final T element;

  private MyComparator(List<T> list, T element) {
    this.list = list;
    this.element = element;
  }

  @Override
  public int compare(T o1, T o2) {
    // Not clear
  }
}
like image 710
JDC Avatar asked Aug 02 '26 23:08

JDC


1 Answers

I think this is what you want :

class ImposedOrder<T> implements Comparator<T> {

    private final List<T> list;
    private final int startIndex;

    ImposedOrder(List<T> list, T startElement) {
        this.list = new ArrayList<>(list);
        this.startIndex = list.indexOf(startElement);
        if (startIndex < 0) {
            throw new IllegalArgumentException();
        }
    }

    @Override
    public int compare(T t1, T t2) {
        int t1Index = list.indexOf(t1);
        int t2Index = list.indexOf(t2);
        return Integer.compare(adjust(t1Index), adjust(t2Index));
    }

    private int adjust(int rawIndex) {
        if (rawIndex >= startIndex) {
            return rawIndex;
        }
        return rawIndex + list.size();
    }
}

Some extra validation may be in order to avoid an imposed order list with duplicates.

The linear search, using indexOf doesn't give you a great performance, but for a small order list it may suffice. Otherwise, rather than saving a copy of the imposed order list, you could map elements to their adjusted index in the Comparator's constructor.

Like this :

class ImposedOrder<T> implements Comparator<T> {

    private final Map<T, Integer> map;
    private final int startIndex;

    ImposedOrder(List<T> list, T startElement) {
        this.startIndex = list.indexOf(startElement);
        if (startIndex < 0) {
            throw new IllegalArgumentException();
        }
        this.map = IntStream.range(0, list.size())
                .boxed()
                .collect(Collectors.toMap(
                        list::get,
                        i -> adjust(startIndex, list.size(), i)
                ));
    }

    @Override
    public int compare(T t1, T t2) {
        Integer t1Index = map.get(t1);
        Integer t2Index = map.get(t2);
        return t1Index.compareTo(t2Index);
    }

    private static int adjust(int startIndex, int size, int rawIndex) {
        if (rawIndex >= startIndex) {
            return rawIndex;
        }
        return rawIndex + size;
    }
}
like image 200
bowmore Avatar answered Aug 05 '26 11:08

bowmore