Как отсортировать Hashmap по ключу на основе пользовательского компаратора

Я получаю Hashmap для общего процесса, который мне нужно отсортировать по ключу с помощью пользовательского компаратора.

Ниже я попробовал, но, похоже, не работает - это не сортировка ключей.

Ключи карты имеют вид long-string-short Пример:(11169-SW-1/11169-SW-2/11132-SH-2/11132-SH-7/11132-SH-1). Сравнение строк не работает так, как мне нужно для числовой части, поэтому у меня есть определенный компаратор. Я понимаю, что пользовательский код компаратора должен быть очищен - например, несколько операторов возврата должны быть объединены, и должна произойти очистка, но я могу это сделать, как только я заставлю его работать.

Как я могу это сделать?

Map<String, Map<String, String>> strValuesMap = Utilities.getDataMapFromDB();

Map<String, Map<String, String>> sortedMap = new TreeMap<>(new CustomComparator());
sortedMap.putAll(strValuesMap);
sortedMap.forEach((x, y) -> System.out.println("id " + x + "=" + y));

Пользовательский компаратор определяется как показано ниже

class CustomComparator implements Comparator<String> {

    @Override
    public int compare(String plId1, String plId2) {

        System.out.println("plId1 : " + plId1 + " plId2 " + plId2);
        String[] plId1Split = plId1.split("-");
        String[] plId2Split = plId2.split("-");
        int retValue = 0;
        if (!plId1Split[0].equalsIgnoreCase(plId2Split[0])) {
            Long seq1 = new Long(plId1Split[0]);
            Long seq2 = new Long(plId2Split[0]);
            retValue = seq1.compareTo(seq1);
        }
        if (retValue != 0) {
            return retValue;
        }
        if (!plId1Split[1].equalsIgnoreCase(plId2Split[1])) {
            retValue = plId1.compareTo(plId2);
        }
        if (retValue != 0) {
            return retValue;
        } else {
            Short seq1 = new Short(plId1Split[2]);
            Short seq2 = new Short(plId2Split[2]);
            retValue = seq1.compareTo(seq2);
            return retValue;
        }
    }
}

Спасибо

1 ответ

Решение

Это не правильно

if (!plId1Split[1].equalsIgnoreCase(plId2Split[1])) {
    retValue = plId1.compareTo(plId2); // This part is wrong
}

В условном теле следует сравнивать только вторую часть строки (plId1Split[1] с участием plId2Split[1]) пока вы сравниваете целые строки (plId1 с участием plId2).
Так значит последняя часть строки (plId1Split[2] а также plId2Split[2]) сравниваются не по порядку номеров, а по лексикографическому порядку.

Так и должно быть:

if (!plId1Split[1].equalsIgnoreCase(plId2Split[1])) {
    retValue = plId1Split[1].compareTo(plId2Split[1]);
}

По поводу ясности вашего компаратора я думаю, что некоторые ваши тесты не требуются.
Например, вы сравниваете с equalsIgnoreCase() и вы сравниваете затем с длинным компаратором ту же строку, преобразованную в long:

if (!plId1Split[0].equalsIgnoreCase(plId2Split[0])) {
        Long seq1 = new Long(plId1Split[0]);
        Long seq2 = new Long(plId2Split[0]);
        retValue = seq1.compareTo(seq1);
} 

В целом, это не очень хорошая оптимизация, если вызываются два сравнения, а также делает код менее читабельным.

Также обратите внимание, что вы злоупотребляете числовыми объектами, которые здесь производят нежелательные операции бокса (Long->long), которые имеют стоимость.
Вы могли бы написать что-то вроде:

class CustomComparator implements Comparator<String> {

    @Override
    public int compare(String plId1, String plId2) {

        String[] plId1Split = plId1.split("-");
        String[] plId2Split = plId2.split("-");

        int retValue = Long.compare(Long.parseLong(plId1Split[0]), 
                               Long.parseLong(plId2Split[0]));

        if (retValue != 0) {
            return retValue;
        }

        retValue = plId1Split[1].compareTo(plId2Split[1]);

        if (retValue != 0) {
            return retValue;
        }

        return Short.compare(Short.parseShort(plId1Split[2]), 
                       Short.parseShort(plId2Split[2]));

   }                    
}

В качестве альтернативы вы можете использовать компараторы Java 8, полагаясь на собственный класс, представляющий объект для сравнения:

// private package if makes sense to make this class not visible outside that
class Identifier {
    private long part1;
    private String part2;
    private short part3;

    Identifier(String[] split) {
        this.part1 =  Long.parseLong(split[0]);
        this.part2 =  split[1];
        this.part3 =  Short.parseShort(split[2]);
    }

    long getPart1() {
        return part1;
    }    
    String getPart2() {
        return part2;
    }
    short getPart3() {
        return part3;
    }
}

И использовать public static <T, U> Comparator<T> comparing( Function<? super T, ? extends U> keyExtractor, Comparator<? super U> keyComparator)

который извлекает ключ сортировки для сравнения элементов и применяет указанный компаратор для этого ключа сортировки:

import static java.util.Comparator.comparing;
import static java.util.Comparator.comparingLong;

Comparator<String> comp =
        comparing(s -> new Identifier(s.split("-")),
                  comparingLong(Identifier::getPart1)
                 .thenComparing(Identifier::getPart2)
                 .thenComparingInt(Identifier::getPart3));

В нем четко представлена ​​сравнительная / функциональная логика.

Обратите внимание, что, поскольку Java не предоставляет встроенные структуры для Tuple, нам необходимо ввести собственный класс для хранения данных.
С классами кортежей (поступающих с https://www.javatuples.org/ или любым другим исходным кодом) код будет еще проще.

Другие вопросы по тегам