-1

I have a set of data and want to sort this data based on its field and their priority in a given config.

priority queue or treeMap with custom comparator based on fields priority can be used.

I have tried the comparator approach but that is not generic enough to handle insertion/removal of the object's field in the priority config.

List<Students> students

1. Student1 : "agc", "2", "12 Aug 2001"
2. Student2 : "acc", "1", "12 Aug 2001"
3. Student3 : "abc", "2", "12 Aug 2003"
{
    field : "name", priority: "2",
    field : "classRank", priority: "1"
}

studentsSorted = new PriorityQueue(students, new CustomComparator());

OR

studentsSorted = students.sort(Comparator.comparing(student::classRank).thenComparing(student::name))

answer : sorted order :

Student2 Student3 Student1

Expectation: generic enough to handle the insertion/removal of fields without code change in the comparator.

Please help.

plug
  • 13
  • 1
  • 3
  • Both `PriorityQueue` and `TreeMap` expect comparator to not change during their lifetime, they'll break otherwise. You will need to construct a new comparator when fields/priority change, clone your data list and sort it with the new comparator. There's no way in Java standard library to do that inplace with an existing collection. If you want that, you'll have to write your own collection. – M. Prokhorov Sep 04 '19 at 10:17

2 Answers2

1

As M.Prokhorov commented, you should not implement PriorityQueue using a comparator that changes behaviour because that will cause it to break. In fact, if any class takes a comparator in its constructor, that comparator must be pure - it's behaviour must never change.

That being said, such a comparator still has its uses, and here is one way to implement it.

class FieldComparator<T> implements Comparator<T> {

    private final List<Function<T, Comparable<?>>> functions = new ArrayList<>();

    public void add(Function<T, Comparable<?>> function) {
        functions.add(function);
    }

    @Override
    @SuppressWarnings("unchecked")
    public int compare(T t1, T t2) {
        for (Function<T, Comparable<?>> f : this) {
            Comparable c1 = f.apply(t1);
            Comparable c2 = f.apply(t2);
            int i = c1.compareTo(c2);
            if (i != 0) return i;
        }
        return 0;
    }
}

This comparator is a list of functions that produce comparable values, and it compares using those functions in the order specified. In this case, the "priority" of the field is determined by list index. So using it like this

public static void main(String[] args) {
    FieldComparator<Student> comparator = new FieldComparator<>();
    comparator.add(Student::getClassRank);
    comparator.add(Student::getName);

    List<Student> students = Arrays.asList(
            new Student("Mark", 2),
            new Student("John", 1),
            new Student("Andy", 2),
            new Student("Uche", 1),
            new Student("Luke", 2),
            new Student("Mary", 1),
            new Student("Lucy", 2)
    );
    students.sort(comparator);
    System.out.println(students);
}

would produce the following result.

[Student{John, 1}, Student{Mary, 1}, Student{Uche, 1}, 
 Student{Andy, 2}, Student{Lucy, 2}, Student{Luke, 2}, Student{Mark, 2}]

But it's not something I would suggest using for a PriorityQueue.

Leo Aso
  • 11,898
  • 3
  • 25
  • 46
  • "_if any class takes a comparator in its constructor, that comparator must be pure_" <- that's not strictly true for _any_ class. JDK classes require that, but some library class may allow setting a different comparator (a different instance!) and then it'll rebuild its contents using the new ordering. – M. Prokhorov Sep 04 '19 at 10:53
  • Also, a note on `FieldComparator`. It shouldn't extend from array list, it should *use* an array list and expose the proper API for what it's supposed to do. A good case for it is made [in this article](https://www.thoughtworks.com/insights/blog/composition-vs-inheritance-how-choose) (the notes about inheritance misuse). – M. Prokhorov Sep 04 '19 at 10:57
  • @M.Prokhorov That's exactly what I was saying. I mean the Comparator instance itself should be pure i.e. a single instance of comparator shouldn't be able to change how it compares values, instead you should have to swap it out for a different instance, that's what I mean by pure. Also, yes, you're right about composition rather than inheritance, that was just me being lazy :). – Leo Aso Sep 04 '19 at 11:32
0

To meet your expectation, write a custom comparator that allows inserting and removing fields to be sorted on. The actual comparing could then be done using reflection. You can get some hints on how to do this here. Please be aware that you'd have to manually resort the collection after changing the comparator.

Guenther
  • 2,035
  • 2
  • 15
  • 20