I want to sort my custom object where it has two fields. 1) Pincode(Long) and 2) Roll(Alphanumeric). I wanted to sort Pincode as in natural ordering but when they are same, then I want to sort them by roll number in descending order.
I'm using Java 8 Comparators and I've the following code.
Model:
public class AreaModel {
public long pinCode;
public String rollNo;
public AreaModel(long pinCode, String rollNo) {
super();
this.pinCode = pinCode;
this.rollNo = rollNo;
}
public long getPinCode() {
return pinCode;
}
public void setPinCode(long pinCode) {
this.pinCode = pinCode;
}
public String getRollNo() {
return rollNo;
}
public void setRollNo(String rollNo) {
this.rollNo = rollNo;
}
@Override
public String toString() {
return "[" pinCode " " rollNo "]";
}
}
Main:
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.javainuse.model.AreaModel;
public class MainClass {
public static void main(String[] args) {
List<AreaModel> list = new ArrayList<AreaModel>();
list.add(new AreaModel(535005, "2"));
list.add(new AreaModel(535006, "100"));
list.add(new AreaModel(535007, "30"));
list.add(new AreaModel(535005, "ROLE-45"));
list.add(new AreaModel(535005, "ROLE-10"));
list.add(new AreaModel(535005,"13"));
list.add(new AreaModel(535005,"70"));
Function<AreaModel, Long> retentionCodeSequence = AreaModel::getPinCode;
Function<AreaModel, String> retentionDuration = AreaModel::getRollNo;
// sort area by pincode, then by role
Comparator<AreaModel> lastThenFirst = Comparator.comparing(retentionCodeSequence).thenComparing(retentionDuration,Comparator.reverseOrder());
list= list.stream().sorted(lastThenFirst).collect(Collectors.toList());
System.out.println("***");
System.out.println(list);
}
}
With that, the output what I'm getting is this
[[535005 ROLE-45], [535005 ROLE-10], [535005 70], [535005 2], [535005 13], [535006 100], [535007 30]]
And what I'm expecting is the following
[[535005 70], [535005 ROLE-45], [535005 13], [535005 ROLE-10], [535005 2], [535006 100], [535007 30]]
As you can see, even though pincodes have been sorted, roles haven't. And a role can contain whole number as a string or a number which is suffixed to a text. How do I resolve it?
Edit: updated question with possible values in the string
CodePudding user response:
Assuming that all the valid values of the property rollNo
have the format "PREFIX-DIGITS"
, i.e. it starts from an optional prefix, containing non-digit characters followed by a dash, and end with a mandatory numeric part, you construct a following comparator, which deals with numeric and non-numeric parts separately.
Comparator<AreaModel> lastThenFirst =
Comparator.comparing(AreaModel::getPinCode)
.thenComparing(Comparator
.<AreaModel, String>comparing(area -> area.getRollNo().replaceAll("\\d", ""), Comparator.reverseOrder())
.thenComparing(area -> Integer.parseInt(area.getRollNo().replaceAll("\\D","")), Comparator.reverseOrder())
);
Note that when there are several instances of AreaModel
having the same pinCode
and one of them has no non-numeric part in its rollNo
value, then this instance would be the last after sorting (in a group having identical pin code). If the opposite behavior is required, you can check this case separately by adding one more comparator to the chain:
Comparator<AreaModel> lastThenFirst =
Comparator.comparing(AreaModel::getPinCode)
.thenComparing(
Comparator.<AreaModel, Boolean>comparing(area -> area.getRollNo().matches("\\d"))
.thenComparing(area -> area.getRollNo().replaceAll("\\d", ""), Comparator.reverseOrder())
.thenComparing(area -> Integer.parseInt(area.getRollNo().replaceAll("\\D", "")), Comparator.reverseOrder())
);
CodePudding user response:
As @g00se mentioned, the integer value in role should be parsed and used in the comparator.
Also, ToLongFunction
could be used to handle pinCode
and default List::sort
should be used to sort current list.
ToLongFunction<AreaModel> pinCode = AreaModel::getPinCode;
ToIntFunction<AreaModel> roleToInt = am -> Integer.parseInt(am.getRollNo().replaceAll("\\D", ""));
Comparator<AreaModel> byRoleDesc = Comparator.comparingInt(roleToInt).reversed();
// sort area by pincode, then by role
Comparator<AreaModel> byPinAndReversedRole = Comparator
.comparingLong(pinCode)
.thenComparing(byRoleDesc);
list.sort(byPinAndReversedRole);
// -> [[535005 70], [535005 ROLE-45], [535005 13], [535005 ROLE-10], [535005 2], [535006 100], [535007 30]]