KennethC
KennethC

Reputation: 756

Java 8 - customised sort based on specific order

I would like to sort the user list based on their status but the order must be based on the order that I set.

I want to set the order of list,

The order should be 1, 0 , 5. We should also keep in mind to order the username as well.

List<User> users = new ArrayList();
         users.add(new User("A", 1));
         users.add(new User("B", 5));
         users.add(new User("C", 0));
         users.add(new User("D", 1));
         users.add(new User("E", 5));
         users.add(new User("F", 0));

Here's the user class

public class User {
         private String username;
         private Integer status;
     }

It should look like this

[
    {
      "username": "A",
      "status": 1
    },
    {
       "username": "D",
       "status": 1
    },
    {
       "username": "C",
       "status": 0
    },
    {
       "username": "F",
       "status": 0
    },
    {
       "username": "B",
       "status": 5
    },
    {
       "username": "E",
       "status": 5
    }
]

I not sure if it's possible to use Comparator.comparing, since this one is neither ascending nor descending order.

Upvotes: 14

Views: 8061

Answers (5)

Mureinik
Mureinik

Reputation: 311393

One approach could be to hold a list with the order you want and sort the users according to its index:

final List<Integer> order = Arrays.asList(1, 0, 5);
users.sort(
    Comparator.comparing((User u) -> order.indexOf(u.getStatus()))
              .thenComparing(User::getUsername));

Note that while this approach should be reasonable for a small number of statuses (like you currently have), it may slow down sorting if there are a large number of statuses and you need to do perform an O(n) search each time. A better performing approach (albeit arguably not as sleek), would be to use a map:

final Map<Integer, Integer> order = new HashMap<>();
order.put(1, 0);
order.put(0, 1);
order.put(5 ,2);
users.sort(Comparator.comparing((User u) -> order.get(u.getStatus()))
                     .thenComparing(User::getUsername));

Upvotes: 16

Abhishek
Abhishek

Reputation: 1618

As you have mentioned you need custom ordering and that means you need to define somewhere that ordering either in HashMap<Status,Rank>> or one simple way add one more attribute say Integer rank; and you can define the rank based on your ordering for status attribute like say users.add(new User("A", 1,0)); here status 1 is sortest in order and its rank=0. And then you can use Comparator on rank attribute.

For e.g :

public class User {
    public String username;
    public Integer status;
    public Integer rank;

    public User(String username, Integer status, Integer rank) 
    { 
        this.username = username; 
        this.status = status; 
        this.rank = rank;
    } 
}

Comparator class :

class SortByRank implements Comparator<User> 
{ 
    // Used for sorting in ascending order of 
    // rank number 
    public int compare(User a, User b) 
    { 
        return a.rank - b.rank; 
    } 
} 

Main Class :

class Main 
{ 
    public static void main (String[] args) 
    { 
         List<User> users = new ArrayList();
         users.add(new User("A", 1, 0));
         users.add(new User("B", 5, 2));
         users.add(new User("C", 0, 1));
         users.add(new User("D", 1, 0));
         users.add(new User("E", 5, 2));
         users.add(new User("F", 0, 1));

        System.out.println("Unsorted"); 
        for (int i=0; i<users.size(); i++) 
            System.out.print(users.get(i).username); 

        Collections.sort(users, new SortByRank()); 

        System.out.println("\nSorted by Rank"); 
        for (int i=0; i<users.size(); i++) 
            System.out.print(users.get(i).username); 
    } 
} 

Upvotes: 0

Dang Nguyen
Dang Nguyen

Reputation: 1209

You can try to do this step by step

//order define here
List<Integer> statusOrder= Arrays.asList(1,0,5,2);

//define sort by status
Comparator<User> byStatus = (u1, u2) -> {
    return Integer.compare(statusOrder.indexOf(u1.getStatus()), statusOrder.indexOf(u2.getStatus()));
};

//define sort by name
Comparator<User> byName = Comparator.comparing(User::getUsername);

//actualy sort
users.sort(byStatus.thenComparing(byName));

Upvotes: 0

Jacob G.
Jacob G.

Reputation: 29700

Assuming 1, 0, and 5 will be the only values of status, AJNeufeld made an excellent point in their comment; they stated that you can use an equation to map each value into an ascending order. In this case, the equation would be (x - 1)^2 where x is the value of status:

users.sort(Comparator.comparingDouble(user -> Math.pow(user.getStatus() - 1, 2)));

If you were to print the contents of user after calling the above snippet, you'd get:

[User [username=A, status=1], User [username=D, status=1], User [username=C, status=0], User [username=F, status=0], User [username=B, status=5], User [username=E, status=5]]

Upvotes: 1

ZhekaKozlov
ZhekaKozlov

Reputation: 39536

If you don't mind using Guava in your project, you can use Ordering.explicit:

users.sort(Ordering.explicit(1, 0, 5).onResultOf(User::getStatus));

If you want to sort by name also, then add thenComparing:

users.sort(Ordering
        .explicit(1, 0, 5)
        .onResultOf(User::getStatus)
        .thenComparing(User::getUsername));

Upvotes: 9

Related Questions