Search code examples
javasortingselenium-webdriver

Descending Sort with underscore in string


I'm trying to perform a descending sort on a column that contains an underscore within the string. This is my descending sort method.

protected void validateDescendingOrder(Integer column) {
    // create a column list
    List<String> columnList = getColumnList(column);

    // create a new list and sort
    List<String> sortedcolumnList = new ArrayList<String>();
    sortedcolumnList.addAll(columnList);
    Collections.sort(sortedcolumnList, new Comparator<String>() {
    public int compare(String o1, String o2) {
        if (o1.contains("_") && o2.contains("_")) {
            return compare(o1.substring(1), o2.substring(1));
        }

        if (o1.contains("_")) {
            return 1;
        }
        if (o2.contains("_")) {
            return -1;
        }

        return o1.compareTo(o2);
    }
});

// sort the list using the custom comparator
Collections.sort(sortedcolumnList, Collections.reverseOrder(String.CASE_INSENSITIVE_ORDER));
//sortedcolumnList.sort(customComparator);
System.out.println(sortedcolumnList);
System.out.println(columnList);
    
// compare the original list order with the sorted list to make sure they match
assertEquals(sortedcolumnList, columnList);

I'm expecting my sort to return Test_jenn, Test_Community, TestRelease, TestCom1, test1, but instead it's returning TestRelease, TestCom1, Test_jenn, Test_Community, test1


Solution

  • The problem

    Collections.sort() is being called twice, and since calling Collections.sort() does re-sort the list, the previous sort is (almost)¹ ignored.

    The posted code is basically:

    Collections.sort(list, comparator1);
    Collections.sort(list, comparator2);
    

    so the resulting order is the one given by comparator2 - reverseOrder(CASE_INSENSITIVE_ORDER).
    The sorting of compartor1 - the anonymous class using underscores - is ignored!

    That is, doing something like:

    Collections.sort(list, Collections.reverseOrder(comparator3));
    

    does not reverse the previous order of the list; it does sort using the given comparator3 in reverse order. In posted code the Comparator being reversed is String.CASE_INSENSITIVE_ORDER, so the list ends up sorted in descending case-insensitive order of the strings.

    Solutions

    Remove the second sort

    Collections.sort(sortedcolumnList, Collections.reverseOrder(String.CASE_INSENSITIVE_ORDER));`
    

    Alternatives to reverse the anonymous Comparator:

    1. use Collections#reverseOrder
    Collections.reverseOrder( new Comparator<String>() {
        public int compare(String o1, String o2) {
            if (o1.contains("_") && o2.contains("_")) {
                return compare(o21.substring(1), o2.substring(1));
            }
    
            if (o2.contains("_")) {
                return 1;
            }
            if (o1.contains("_")) {
                return -1;
            }
    
            return o2.compareTo(o1);
        }
    } )
    
    1. just swap the arguments of compare
      caution: do not swap the arguments of the recursive call!
    new Comparator<String>() {
        public int compare(String o1, String o2) {
            if (o1.contains("_") && o2.contains("_")) {
                return compare(o21.substring(1), o2.substring(1));
            }
    
            if (o2.contains("_")) {
                return 1;
            }
            if (o1.contains("_")) {
                return -1;
            }
    
            return o2.compareTo(o1);
        }
    }
    
    1. or invert the result
      caution: do not invert the result of the recursive call!
    new Comparator<String>() {
        public int compare(String o1, String o2) {
            if (o1.contains("_") && o2.contains("_")) {
                return compare(o1.substring(1), o2.substring(1));
            }
    
            if (o1.contains("_")) {
                return -1;
            }
            if (o2.contains("_")) {
                return 1;
            }
    
            return - o1.compareTo(o2);
        }
    }
    

    Notes

    1. Case Insensitive
      To obtain a case insensitive ordering in above codes, use compareToIgnoreCase() instead of ignoreTo().

    2. Recursive Call
      I am not sure what is the intention of the recursive call of compare. For me it seems to be an expensive and error-prone way for ignoring everything before the underscores of both strings. The compare method can eventually be called to compare each element with each other element, that is, it can be called very often, a non-recursive solution would be more indicated. Suggestion: instead of contains use indexOf() to search the underscores and substring() to ignore everything up to that index.

    as I wrote, not sure what the exact requirement are.


    1 - Collections#sort "is guaranteed to be stable: equal elements will not be reordered as a result of the sort." (javadoc)