Reputation: 187
Consider the dto class below -
public class RoleDto {
private String name;
RoleDto(String name) {
this.name = name;
}
}
Its instances would contain names like -
Business Practitioner - Expert
Developer - Expert
Developer - Master
Business Practitioner - Master
Business Practitioner
Developer
Developer - Professional
Business Practitioner - Professional
I want to sort the instances such that the order is -
Business Practitioner - Expert
Business Practitioner - Master
Business Practitioner - Professional
Business Practitioner
Developer - Expert
Developer - Master
Developer - Professional
Developer
I have used the code below -
ArrayList<RoleDto> roles = new ArrayList<>();
roles.add(new RoleDto("Business Practitioner - Expert"));
roles.add(new RoleDto("Developer - Expert"));
roles.add(new RoleDto("Developer - Master"));
roles.add(new RoleDto("Business Practitioner - Master"));
roles.add(new RoleDto("Business Practitioner"));
roles.add(new RoleDto("Developer"));
roles.add(new RoleDto("Developer - Professional"));
roles.add(new RoleDto("Business Practitioner - Professional"));
List<RoleDto> sorted = roles.stream().sorted(comparing(r -> r.getName(),
comparing((String s) -> !s.contains("-")))).collect(toList());
for(RoleDto r : sorted)
System.out.println(r.getName());
However the output is
Business Practitioner - Expert
Developer - Expert
Developer - Master
Business Practitioner - Master
Developer - Professional
Business Practitioner - Professional
Business Practitioner
Developer
Can someone please help me achieve the expected result
Upvotes: 0
Views: 1540
Reputation: 338336
It seems you have two dimensions of data at play here.
Trying to represent such values as text is clumsy, and ignores the features built into Java for such a purpose.
If all the values are known at compile-time, make an enum for each dimension. An enum is a class defined in such a way as to automatically instantiate and name several objects when the class is loaded.
public enum Function { BUSINESS_PRACTITIONER, PROGRAMMER ; }
public enum Proficiency { EXPERT, MASTER, PROFESSIONAL, NOVICE; }
Pull those together into a single class. The primary purpose of this class is to carry data transparently and immutably. So we can define briefly as a record.
public record Role( Function function , Proficiency proficiency ) { }
We want instances of Role
to sort in two levels, first by Function
, then by Proficiency
. So we need to implement the Comparable
interface on our record Role
.
public record Role( Function function , Proficiency proficiency ) implements Comparable<Role> { … }
The sort order of each is the order in which the named objects are declared. So we can write the necessary compareTo
method using a Comparator
object. We define that Comparator
object using convenience methods comparing
and thenComparing
, passing each a method reference. We mark this object as a singleton using static
, for frequent re-use.
private static Comparator < Role > comparator =
Comparator
.comparing( Role :: function )
.thenComparing( Role :: proficiency );
We use that comparator in our compareTo
method.
@Override public int compareTo ( Role other ) { return Role.comparator.compare( this , other ); }
We instantiate a role object like this:
Role progPro = new Role( Function.PROGRAMMER , Proficiency.PROFESSIONAL );
If need be, we can make a set of all possible roles.
NavigableSet < Role > allPossibleRoles = new TreeSet();
for ( Function function : Function.values() )
{
for ( Proficiency proficiency : Proficiency.values() )
{
allPossibleRoles.add( new Role( function , proficiency ) );
}
}
System.out.println( "allPossibleRoles = " + allPossibleRoles );
We can see these are listed in the order dictated by the Question.
allPossibleRoles = [Role[function=BUSINESS_PRACTITIONER, proficiency=EXPERT], Role[function=BUSINESS_PRACTITIONER, proficiency=MASTER], Role[function=BUSINESS_PRACTITIONER, proficiency=PROFESSIONAL], Role[function=BUSINESS_PRACTITIONER, proficiency=NOVICE], Role[function=PROGRAMMER, proficiency=EXPERT], Role[function=PROGRAMMER, proficiency=MASTER], Role[function=PROGRAMMER, proficiency=PROFESSIONAL], Role[function=PROGRAMMER, proficiency=NOVICE]]
Lastly, we need to emit the text seen in the Question.
Modify the enum classes to take an argument for a display name.
public enum Function
{
BUSINESS_PRACTITIONER( "Business Practitioner" ), PROGRAMMER( "Programmer" );
private final String displayName;
// Constructor
Function ( final String displayName ) { this.displayName = displayName; }
public String getDisplayName ( ) { return displayName; }
}
And the other enum.
public enum Proficiency
{
EXPERT( "Expert" ), MASTER( "Master" ), PROFESSIONAL( "Professional" ), NOVICE( "" );
private final String displayName;
// Constructor
Proficiency ( String displayName ) { this.displayName = displayName; }
public String getDisplayName ( ) { return displayName; }
}
Modify the Role
record to generate text in desired format with a getDisplayName
method.
import java.util.Comparator;
public record Role( Function function , Proficiency proficiency ) implements Comparable < Role >
{
@Override
public int compareTo ( Role other ) { return Role.comparator.compare( this , other ); }
private static Comparator < Role > comparator =
Comparator
.comparing( Role :: function )
.thenComparing( Role :: proficiency );
public String getDisplayName ( )
{
String x = this.function.getDisplayName();
String y = this.proficiency.getDisplayName().isBlank() ? "" : " - ";
String z = this.proficiency.getDisplayName();
return x + y + z;
}
}
Example usage.
NavigableSet < Role > allPossibleRoles = new TreeSet(); // NavigableSet/TreeSet keeps elements sorted.
for ( Function function : Function.values() )
{
for ( Proficiency proficiency : Proficiency.values() )
{
allPossibleRoles.add( new Role( function , proficiency ) );
}
}
// Dump to console.
for ( Role role : allPossibleRoles )
{
System.out.println( "role.getDisplayName() = " + role.getDisplayName() );
}
role.getDisplayName() = Business Practitioner - Expert
role.getDisplayName() = Business Practitioner - Master
role.getDisplayName() = Business Practitioner - Professional
role.getDisplayName() = Business Practitioner
role.getDisplayName() = Programmer - Expert
role.getDisplayName() = Programmer - Master
role.getDisplayName() = Programmer - Professional
role.getDisplayName() = Programmer
Upvotes: 1
Reputation: 2280
You do not actually compare the names, the compare just checks whether or not the names contain "-", which is why you are getting all that do (in random order) before all that don't (in random order). If you can change the Dto, see the answer of @Orr, otherwise you could do the name split/compare in place, like this:
final List<RoleDto> sorted =
roles.stream()
.sorted(
comparing(
RoleDto::getName,
comparing((String s) -> s.split(" - ")[0])
.reversed()
.thenComparing(s -> s.split(" - ").length)
.reversed()
.thenComparing(s -> s.split(" - ")[1])))
.collect(toList());
The reversing may seem a little unintuitive, but effectively every reverse reverses all the previous comparisons, so the first comparison get reversed twice. To skip that, you could instead write:
final List<RoleDto> sorted =
roles.stream()
.sorted(
comparing(
RoleDto::getName,
comparing((String s) -> s.split(" - ")[0])
.thenComparing(s -> -s.split(" - ").length)
.thenComparing(s -> s.split(" - ")[1])))
.collect(toList());
(notice the minus sign in the middle compare)
Upvotes: 1
Reputation: 405
I would suggest changing your RoleDto to have two attributes: String role and String level
Now the code for sorting List would look like:
Comparator<RoleDto> compareByRoleAndLevel = Comparator
.comparing(RoleDto::getRole)
.thenComparing(RoleDto::getLevel);
List<RoleDto> sortedRoles = roles.stream()
.sorted(compareByRoleAndLevel)
.collect(Collectors.toList());
Upvotes: 3