Sort string containing letters and numbers

I have a list of objects that has an integer and a String (consisting of letters and numbers). I would like to sort by this integer and as second clause the alphabetical/numeric order of the string, so I tried to use method sort of Class Collections.

Map Class:

public class Mapa {
    private Integer sequencia;
    private String lote;

    //get e set
    @Override
    public String toString() {
        return "Mapa [sequencia=" + sequencia + ", lote=" + lote + "]";
    }
}

Test Class:

public class App {

    public static void main(String[] args) throws IOException {
        List<Mapa> mapas = new ArrayList<Mapa>();

        for (int i = 50; i > 0; i--) {
            Mapa mapa = new Mapa();
            mapa.setSequencia(1);
            mapa.setLote("Lote 00"+i);
            mapas.add(mapa);
        }

        Collections.sort(mapas, new Comparator<Mapa>() {

            public int compare(Mapa arg0, Mapa arg1) {
                Integer numSeqArg0 = arg0.getSequencia();
                String loteArg0 = arg0.getLote();

                Integer numSeqArg1 = arg1.getSequencia();
                String loteArg1 = arg1.getLote();

                Integer compareNumSequencia = numSeqArg0.compareTo(numSeqArg1);
                Integer compareLote = loteArg0.compareTo(loteArg1);

                return compareNumSequencia == 0 ? compareLote : compareNumSequencia;
            }
        });

        for (Mapa mapa : mapas) {
            System.out.println(mapa);
        }
    }
}

Result:

Mapa [sequencia=1, lote=Lote 1]
Mapa [sequencia=1, lote=Lote 10]
Mapa [sequencia=1, lote=Lote 11]
Mapa [sequencia=1, lote=Lote 12]
Mapa [sequencia=1, lote=Lote 13]
Mapa [sequencia=1, lote=Lote 14]
Mapa [sequencia=1, lote=Lote 15]
Mapa [sequencia=1, lote=Lote 16]
Mapa [sequencia=1, lote=Lote 17]
Mapa [sequencia=1, lote=Lote 18]
Mapa [sequencia=1, lote=Lote 19]
Mapa [sequencia=1, lote=Lote 2]
Mapa [sequencia=1, lote=Lote 20]
Mapa [sequencia=1, lote=Lote 21]
Mapa [sequencia=1, lote=Lote 22]
Mapa [sequencia=1, lote=Lote 23]
Mapa [sequencia=1, lote=Lote 24]
Mapa [sequencia=1, lote=Lote 25]
Mapa [sequencia=1, lote=Lote 26]
Mapa [sequencia=1, lote=Lote 27]
Mapa [sequencia=1, lote=Lote 28]
Mapa [sequencia=1, lote=Lote 29]
Mapa [sequencia=1, lote=Lote 3]
Mapa [sequencia=1, lote=Lote 30]
Mapa [sequencia=1, lote=Lote 31]
Mapa [sequencia=1, lote=Lote 32]
Mapa [sequencia=1, lote=Lote 33]
Mapa [sequencia=1, lote=Lote 34]
Mapa [sequencia=1, lote=Lote 35]
Mapa [sequencia=1, lote=Lote 36]
Mapa [sequencia=1, lote=Lote 37]
Mapa [sequencia=1, lote=Lote 38]
Mapa [sequencia=1, lote=Lote 39]
Mapa [sequencia=1, lote=Lote 4]
Mapa [sequencia=1, lote=Lote 40]
Mapa [sequencia=1, lote=Lote 41]
Mapa [sequencia=1, lote=Lote 42]
Mapa [sequencia=1, lote=Lote 43]
Mapa [sequencia=1, lote=Lote 44]
Mapa [sequencia=1, lote=Lote 45]
Mapa [sequencia=1, lote=Lote 46]
Mapa [sequencia=1, lote=Lote 47]
Mapa [sequencia=1, lote=Lote 48]
Mapa [sequencia=1, lote=Lote 49]
Mapa [sequencia=1, lote=Lote 5]
Mapa [sequencia=1, lote=Lote 50]
Mapa [sequencia=1, lote=Lote 6]
Mapa [sequencia=1, lote=Lote 7]
Mapa [sequencia=1, lote=Lote 8]
Mapa [sequencia=1, lote=Lote 9]

As we can see, the string is getting sorted incorrectly. How can I sort a string by alphabetical order, which also obeys the numbers that compose it?

Author: hkotsubo, 2019-05-10

1 answers

When you compare strings, even the digits are compared taking into account the lexicographic order of the characters (i.e. their numerical value is not taken into account, instead a comparison is made between the value of the Unicode code points of the characters).

In the same way that "abacate" comes before "abelha" (because the first and second letters are equal, but the third makes the "tiebreaker"), "Lote 40" comes before "Lote 5", because the first 5 characters are equal ("lot" and space), and the character 4 makes the tiebreaker (since lexicographically it comes before 5).

If you want to compare the string and its numerical value, this must be done separately. You can use split to break the string into two parts, compare the first ("batch") as a string and the second as a number:

Collections.sort(mapas, new Comparator<Mapa>() {
    public int compare(Mapa arg0, Mapa arg1) {
        Integer numSeqArg0 = arg0.getSequencia();
        Integer numSeqArg1 = arg1.getSequencia();
        int compareNumSequencia = numSeqArg0.compareTo(numSeqArg1);
        if (compareNumSequencia != 0) {
            // se número é diferente, não precisa comparar os lotes
            return compareNumSequencia;
        }
        String[] lote0Partes = arg0.getLote().split(" ");
        String[] lote1Partes = arg1.getLote().split(" ");

        int compareLote = lote0Partes[0].compareTo(lote1Partes[0]);
        int compareLoteNum = Integer.parseInt(lote0Partes[1]) - Integer.parseInt(lote1Partes[1]);

        return compareLote == 0 ? compareLoteNum : compareLote;
    }
});

split separates the string by spaces, so the return is an array containing two strings: Lote and the respective number. In then I compare the first part, and if they are equal I use Integer.parseInt to turn the second part into a number and I do the comparison according to the numerical values.

Also notice that the methods compareTo return a int, so you don't have to assign the return to a Integer (Java does the auto-boxing automatically, but in this case we will only compare the numerical values afterwards, so you can use int directly).

 2
Author: hkotsubo, 2019-05-10 23:21:29