Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to sort objects by two fields both in reversed natural order using a Comparator.comparingInt chain

Lets say I want to sort objects in an ArrayList on a field height in reversed order and if two values are the same I want to further sort on the field width also in reversed order. Is there a way by using something like

 Comparator<Test> comparator = Comparator
            .comparingInt((Test t) -> t.height).reversed()
            .thenComparingInt((Test t ) -> t.width).reversed();

I know I could use something like:

Collections.sort(list, new Comparator<Test>() {

        public int compare(Test o1, Test o2) {

            Integer x1 =  o1.height;
            Integer x2 =  o2.height;
            int sComp = x2.compareTo(x1);

            if (sComp != 0) {
                return sComp;
            }

            x1 = o1.width;
            x2 = o2.width;
            return x2.compareTo(x1);
        }});

But I'm really curious if there is one line solution

So regarding this mini-example

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class Main {

    public static void main(String[] args) {
        Test one = new Test();
        one.height = 2;
        one.width = 1;
        Test two = new Test();
        two.height = 2;
        two.width = 3;
        Test three = new Test();
        three.height = 1;
        three.width = 1;

        Comparator<Test> comparator = Comparator
            .comparingInt((Test t) -> t.height).reversed()
            .thenComparingInt((Test t ) -> t.width).reversed();

        List<Test> list = new ArrayList<>();
        list.add(one);
        list.add(two);
        list.add(three);

        list.stream()
            .sorted(comparator)
            .forEach(e -> System.out.println(e.height + "/" + e.width));
    }
}

class Test {
    int width;
    int height;
}

I get the output:

1/1
2/3
2/1

because the second reversed() reverses the whole list. Is there a way to gean a output of:

2/3
2/1
1/1
like image 548
Rotzlucky Avatar asked Jan 04 '19 22:01

Rotzlucky


2 Answers

just remove reversed() from comparingInt and only call reversed upon thenComparingLong:

Comparator<Test> comparator = 
      Comparator.comparingInt((Test t) -> t.height) // <--- removed reverse from this comparator
                .thenComparingLong((Test t ) -> t.width).reversed();

Further, given that width is an int I'd use thenComparingInt instead of thenComparingLong.

In addition, in regard to your stream pipeline, I'd suggest using forEachOrdered since you care about the order in which the elements are printed.

forEach is documented as:

The behavior of this operation is explicitly nondeterministic. For parallel stream pipelines, this operation does not guarantee to respect the encounter order of the stream, as doing so would sacrifice the benefit of parallelism.

Therefore:

 list.stream()
     .sorted(comparator)
     .forEachOrdered(e -> System.out.println(e.height + "/" + e.width));
like image 170
Ousmane D. Avatar answered Sep 16 '22 22:09

Ousmane D.


To complete the very good answer of Aomine I will expose the possibilities and the behavior behind them.
Note that you should favor getters (and also method references) over direct field access. So I will illustrate with that. I will also rely on static import for Comparator static methods such as import static java.util.Comparator.*; to focus on important things.

What you do actually cancels the initial reversed Comparator on getHeight():

Comparator<Test> comparator = 
       comparingInt(Test::getHeight)
       .reversed() // 1)
       .thenComparingInt(Test::getWidth) // 2)
       .reversed(); // 3)

In terms of logic it means :

1) Sort by comparing on the reverse of Test::getHeight.
2) Then sort by comparing on Test::getWidth.
3) Reverse the whole comparing logic.

So you get a Comparator that sorts on Test::getHeight and then sorts on the reverse of Test::getWidth.

In the solution provided by Aomine :

Comparator<Test> comparator = 
       comparingInt(Test::getHeight) // 1)
       .thenComparingInt(Test::getWidth) // 2)
       .reversed(); // 3)

In terms of logic it means :

1) Sort by comparing on Test::getHeight.
2) Then sort by comparing on Test::getWidth.
3) Reverse the whole comparing logic.

So you get a Comparator that sorts on the reverse of Test::getHeight and then sorts on the reverse of Test::getWidth.

You could also write the code in this way (while more verbose but interesting in terms of learning) :

Comparator<Test> comparator = 
       comparingInt(Test::getHeight) 
       .reversed() // 1)
       .thenComparing(comparingInt(Test::getWidth)
                      .reversed()); // 2)

In terms of logic it means :

1) Sort by comparing on the reverse of Test::getHeight.
2) Then sort by comparing on the reverse of Test::getWidth.

That produces still a Comparator that sorts on the reverse of Test::getHeight and then sorts on the reverse of Test::getWidth.

like image 27
davidxxx Avatar answered Sep 16 '22 22:09

davidxxx