0

I have this function that sorts my lines of data by numerical order, but the problem is that the key it sorts by composed from letters and numbers:

void writeData(Map<String, Params> data) {
    .
    .
    StringBuilder sb = new StringBuilder();
    data.values()
        .stream()
        .sorted(Comparator.comparing(Params::getIdx))
        .forEach(r -> {
            sb.append(System.lineSeparator());
            sb.append(r.getCSVRow());
        });
    .
    .
}

For example I have the strings: "A100","A101", "A99".

The order I get:

A100
A101
A99

The order I want:

A99
A100
A101

How can I change this function? I don't have an option to use thenCompare for some reason. (I've read about it though).

gus
  • 71
  • 4

2 Answers2

1

Lexicographically this is the correct sorting, because character 9 goes after character 1. You should split these strings into non-numeric and numeric parts and sort them separately: numbers as numbers, and strings as strings. After sorting you can join these parts back into one string and get a sorted array. For example:

// assume a string consists of a sequence of
// non-numeric characters followed by numeric characters
String[] arr1 = {"A100", "A101", "A99", "BBB33", "C10", "T1000"};
String[] arr2 = Arrays.stream(arr1)
        // split string into an array of two
        // substrings: non-numeric and numeric
        // Stream<String[]>
        .map(str -> str
                // add some delimiters to a non-empty
                // sequences of non-numeric characters
                .replaceAll("\\D+", "$0\u2980")
                // split string into an array
                // by these delimiters
                .split("\u2980"))
        // parse integers from numeric substrings,
        // map as pairs String-Integer
        // Stream<Map.Entry<String,Integer>>
        .map(row -> Map.entry(row[0], Integer.parseInt(row[1])))
        // sort by numbers as numbers
        .sorted(Map.Entry.comparingByValue())
        // intermediate output
        //C=10
        //BBB=33
        //A=99
        //A=100
        //A=101
        //T=1000
        .peek(System.out::println)
        // join back into one string
        .map(entry -> entry.getKey() + entry.getValue())
        // get sorted array
        .toArray(String[]::new);
// final output
System.out.println(Arrays.toString(arr2));
// [C10, BBB33, A99, A100, A101, T1000]

See also:
How do I relate one array input to another?
How to remove sequence of two elements from array or list?

0

Parse the number out:

Comparator.comparingInt(v -> Integer.parseInt(v.substring(1))

(assuming the prefix is always 1 character)

Andy Turner
  • 137,514
  • 11
  • 162
  • 243
  • It is not I'm afraid. But it is actually two characters, then a coma and then the number... – gus Feb 11 '21 at 20:06
  • That's not what you showed in the question. Nonetheless, a prefix of two characters and a comma has a fixed length of 3, so you can just change the `1` to `3`. – Andy Turner Feb 12 '21 at 09:03
  • What I get now is that 26 comes before 3 for some reason (for example). I get this ordering: 26, 27, 28, 29, 3, 30, 31, 32. While I want: 3, 26, 27, 28, 29, 30, 31, 32. Can you please assist? – gus Feb 18 '21 at 01:11
  • @gus `Stream.of("AA,3", "AA,26").sorted(Comparator.comparingInt(v -> Integer.parseInt(v.substring(3)))).forEach(System.out::println)` would print in the order `AA,3 AA,26`: https://ideone.com/G9GzKA. I don't know what you've done to get the order you report. – Andy Turner Feb 18 '21 at 13:32