Home > OS >  Sort users based on initials with Java Stream API. If initials repeat, sort based on user's age
Sort users based on initials with Java Stream API. If initials repeat, sort based on user's age

Time:06-18

I am trying to sort users based on their initials, and when initials are repeated, say 'Alex Fay' and 'Alba Finn', I have to sort them based on their age in descending order.

This is my current code:

List<SortedUser> sortedUserList = list.stream()
                .sorted((a, b) -> {
                 
                    int first = a.getName().charAt(0)   a.getLastName().charAt(0);
                    int second = b.getName().charAt(0)   b.getLastName().charAt(0);

                    if(first > second) {
                        return -1;
                    }
                    if(first < second) {
                        return 1;
                    }

                    return b.getAge() - a.getAge();
                }).collect(Collectors.toList()); 

The problem with this code is that when I have initials 'AR' and 'EN', their sums are the same (147) - therefore first == second, and they appear one after the other in incorrect order. I also tried to compare the first name initial and the last name initial separately, but I still couldn't get it to work. What can I change in order to sort them the way I want it?

Sample input:

(name=Alexandre, lastName=Wuckert, age=66),

(name=Allan, lastName=Wehner, age=84),

(name=Bradley, lastName=Thompson, age=78),

(name=Bernice, lastName=Schoen, age=63)

Sample output:

(name=Bradley, lastName=Thompson, age=78),

(name=Bernice, lastName=Schoen, age=63),

(name=Allan, lastName=Wehner, age=84),

(name=Alexandre, lastName=Wuckert, age=66)

CodePudding user response:

Adding characters to sort on is unreliable since many pairs can sum to the same value. So you should build a String of initials rather than add them. Then use the following in your sort method.

String first = a.getName().charAt(0)   ""   a.getLastName().charAt(0);
String second = b.getName().charAt(0)  ""   b.getLastName().charAt(0);
int result = first.compareTo(second);
    
return result > 0 ? -1 : result < 0 ? 1 : b.getAge-a.getAge();

The above will sort in descending order based on the initials. If result == 0 then the ages will be used (as you did before).

Note: you could also do second.compareTo(first) and then either swap 1 and -1 or swap the inequality signs. Which ever makes more sense to you.

Here are some other alternatives for your consideration. I used a record to hold the User data.

record User(String getName, String getLastName, int getAge) {
}

List<User> list =
        new ArrayList<>(List.of(new User("Alexandre", "Wuckert", 66),
                new User("Allan", "Wehner", 84),
                new User("Bradley", "Thompson", 78),
                new User("Bernice", "Schoen", 63)));
  • stream the list of users.
  • the first argument is a keyExtractor to build the String on which to sort. In this case it is the user's initials.
  • then the String comparator, String::compareTo is used to sort on the initials.
  • if the initials are equal, then compare on the user's age.
  • reversed() reverses are previous comparators to change from ascending (default) to descending.
List<User> sortedUsers = list.stream().sorted(Comparator
        .comparing(
                (User u) -> u.getName().charAt(0)   ""
                          u.getLastName().charAt(0),
                String::compareTo)
        .thenComparing(User::getAge).reversed()).toList();

sortedUsers.forEach(System.out::println);

prints

User[getName=Bradley, getLastName=Thompson, getAge=78]
User[getName=Bernice, getLastName=Schoen, getAge=63]
User[getName=Allan, getLastName=Wehner, getAge=84]
User[getName=Alexandre, getLastName=Wuckert, getAge=66]

Note that you could also just sort the list in place and not use streams as long as the list is mutable.

list.sort(Comparator.comparing(
                 (User u) -> u.getName().charAt(0)   ""
                             u.getLastName().charAt(0),
                             String::compareTo)
                .thenComparing(User::getAge).reversed());

CodePudding user response:

To make the code more readable and less error-prone, use Java 8 static methods of the Comparator interface instead of placing a chain of conditions into a lambda expression.

Here's another way of implementing a comparator that will compare users based on the first letter of the first name, then by the first letter of the last name and finally by age in reversed:

Comparator<SortedUser> byFirstLettersAndAgeReversed =
    Comparator.comparingInt((SortedUser user) -> user.getFirstName().charAt(0))
        .thenComparingInt(user -> user.getLastName().charAt(0))
        .thenComparingInt(SortedUser::getAge).reversed();


 List<SortedUser> sortedUserList = list.stream()
    .sorted(byFirstLettersAndAgeReversed)
    .collect(Collectors.toList());

You can define a couple of comparators as public static fields inside the reuse them you need to sort or compare these objects in a specific way.

  • Related