Home > database >  How to sort strings where a string contains alpha numeric characters like ROLL-10 > ROLL-2
How to sort strings where a string contains alpha numeric characters like ROLL-10 > ROLL-2

Time:09-23


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]]
  • Related