Search code examples
javasortingcomparator

Best way to sort bracketed values in a string both alphabetically and by number?


I have an ArrayList that contains a list of JSONpaths in String format, similar to something like the following:

"$['book'][0]['title']"
"$['book'][1]['title']"
"$['book'][2]['title']"
...
"$['book'][10]['title']"
"$['movie']['series'][0]['title']"
"$['movie']['series'][1]['title']"
"$['movie']['series'][2]['title']"
...
"$['movie']['series'][10]['title']"

Don't worry about the actual data here, it's garbage, the ultimate structure is $ with a series of brackets where sometimes the value in between the brackets is an alphabetic string and sometimes it is a number.

I need to sort those ArrayList items in order, where if the node in between the brackets is in quotes it is sorted alphabetically, but if it is outside quotes, it should be sorted as a number. I.e. the order should be:

"$['book'][0]['title']"
"$['book'][1]['title']"
"$['book'][2]['title']"
"$['book'][10]['title']"
"$['movie']['series'][0]['title']"
"$['movie']['series'][1]['title']"
"$['movie']['series'][2]['title']"
"$['movie']['series'][10]['title']"

not:

"$['book'][0]['title']"
"$['book'][1]['title']"
"$['book'][10]['title']"
"$['book'][2]['title']"
"$['movie']['series'][0]['title']"
"$['movie']['series'][1]['title']"
"$['movie']['series'][10]['title']"
"$['movie']['series'][2]['title']"

I expect I need to implement a custom Comparator for this, but I'm having trouble coming up with the most efficient way to parse these strings and do the sorting. Does anyone have a suggested approach for this?


Solution

  • Custom comparator for the strings should convert the input strings into lists/arrays which could be compared then by the appropriate elements at the same indexes:

    String[] data = {
            "$['book'][0]['title']",
            "$['book'][0]['title']['part1']",
            "$['book'][0]['title']['2']",
            "$['book'][1]['title']",
            "$['book'][prequel]['title']",
            "$['book'][sequel]['title']",
            "$['movie']['series'][1]['title']",
            "$['book'][10]['title']",
            "$['movie']['series'][10]['title']",
            "$['book'][2]['title']",
            "$['movie']['series'][0]['title']",
            "$['movie']['series'][2]['title']"
    };
    
    Arrays.sort(data, MyClass::customCompare);
    

    Method customCompare should call convertor and apply conditional comparison "for integers" if needed:

    private static int customCompare(String s1, String s2) {
        List<String> l1 = convert(s1);
        List<String> l2 = convert(s2);
        int res = 0;
        for (int i = 0, n = Math.min(l1.size(), l2.size()); res == 0 && i < n; i++) {
            String e1 = l1.get(i);
            String e2 = l2.get(i);
            if (e1.matches("\\d+") && e2.matches("\\d+")) {
                res = Integer.compare(Integer.valueOf(e1), Integer.valueOf(e2));
            } else {
                res = e1.compareTo(e2);
            }
        }
        return res != 0 ? res : Integer.compare(l1.size(), l2.size());
    }
    

    Method convert cleans out unnecessary characters and creates a list of strings from the input:

    private static List<String> convert(String str) {
        return Arrays.stream(str.split("[$'\\[\\]]"))
                     .filter(x -> !x.isEmpty())
                     .collect(Collectors.toList());
    }
    

    Output after sorting:

    $['book'][0]['title']
    $['book'][0]['title']['2']
    $['book'][0]['title']['part1']
    $['book'][1]['title']
    $['book'][2]['title']
    $['book'][10]['title']
    $['book'][prequel]['title']
    $['book'][sequel]['title']
    $['movie']['series'][0]['title']
    $['movie']['series'][1]['title']
    $['movie']['series'][2]['title']
    $['movie']['series'][10]['title']