Выборочная сортировка таким образом, что A предшествует a, а B - перед b

11

У меня есть список цветов, как это:

Розовый, синий, красный, синий, серый, зеленый, фиолетовый, черный ... и т. Д.

List<String> listOfColors =  Arrays.asList("Pink", "Blue", "Red", "blue", "Grey", "green", "purple", "black");

Есть некоторые промежуточные операции, такие как фильтрация цветов фруктов, теперь у меня остались отфильтрованные результаты, в которых я хочу отсортировать их по порядку:

Синий, черный, синий, серый, зеленый, розовый, фиолетовый, красный

Я пытался :

List<String> collect = listOfColors.stream().sorted(String::compareToIgnoreCase)
        .collect(Collectors.toList());

Это не работает, как ожидалось.

Вывод следующий:

черный, синий, синий, зеленый, серый, розовый, фиолетовый, красный

Я хочу следующее:

Синий, черный, синий, серый, зеленый, розовый, фиолетовый, красный

Вишва Ратна
источник
2
Разве черный не должен предшествовать синему, а зеленый - серому?
Равиндра Ранвала
3
aраньше, uтак что результат правильный
Йенс
2
@RavindraRanwala, синий «s B является столицей , но обратно » s б не является.
Вишва Ратна
2
Я пытался, но это не дает этот конкретный порядок. Это дает [black, Blue, blue, green, Grey, Pink, purple, Red]@ chrylis-onstrike-
Равиндра Ранвала
2
Если вы хотите, чтобы корпус с прописной буквы находился до нижнего, то игнорирование корпуса - это последнее, что вам нужно.
Teepeemm

Ответы:

8

Мое решение состоит в том, чтобы использовать сортировку в два этапа, используя Comparator.thenComparing()метод.

Сначала сравните строки только по первому символу, игнорируя регистр. Таким образом, группы с одинаковым первым символом (независимо от того, в каком случае) пока остаются несортированными. Затем на втором шаге примените обычную сортировку по алфавиту, чтобы отсортировать эти несортированные подгруппы.

List<String> listOfColors =  Arrays.asList("Pink", "Blue", "Red", "blue", "Grey", "green", "purple", "black");
Comparator<String> comparator = Comparator.comparing(s -> 
        Character.toLowerCase(s.charAt(0)));
listOfColors.sort(comparator.thenComparing(Comparator.naturalOrder()));
System.out.println(listOfColors);

Может быть, это еще можно оптимизировать, но это дает желаемый результат:

[Blue, black, blue, Grey, green, Pink, purple, Red]

DanielBK
источник
Сделано редактирование для удобства чтения Comparator. Но да, это предполагает только сравнение первого символа строки, о котором ОП особо не подчеркивал.
Наман
8

Вы можете использовать RuleBasedCollator для определения ваших собственных правил.

Пример пользовательского правила:

String rules = "< c,C < b,B";

Вышеуказанное правило расшифровывается так, что при сравнении строк прописные и строчные буквы Cдолжны появляться перед прописными и строчными B.

String customRules = "<A<a<B<b<C<c<D<d<E<e<F<f<G<g<H<h<I<i<J<j<K<k<L<l<M<m<N<n<O<o<P<p<Q<q<R<r<S<s<T<t<U<u<V<v<X<x<Y<y<Z<z";
RuleBasedCollator myRuleBasedCollator = new RuleBasedCollator(customRules);
Collections.sort(listOfColors,myRuleBasedCollator);
System.out.println(listOfColors);

Вывод:

[Blue, black, blue, Grey, green, Pink, purple, Red]

Редактировать: вместо написания customRulesот руки, вы можете использовать ниже код для его генерации.

String a = IntStream.range('a', 'z' + 1).mapToObj(c -> Character.toString((char) c))
        .flatMap(ch -> Stream
            .of("<", ch.toUpperCase(), "<", ch)).collect(Collectors.joining(""));
Вишва Ратна
источник
2
создание String customRulesможет быть автоматизмом с IntStream:IntStream.range('a', 'z' + 1) .mapToObj(Character::toString) .flatMap(ch -> Stream.of("<", ch.toUpperCase(), "<", ch)) .collect(Collectors.joining(""))
lczapski
@ lczapski, как- .mapToObj(Character::toString)то не будет решена, я думаю, вам нужно использовать.mapToObj(c -> Character.toString((char) c))
Вишва Ратна
mapToObj(Character::toString)работает только в Java 11 или новее.
Хольгер
@Holger Хорошо, я попробовал свою Систему (JDK-8) и mapToObj(Character::toString)не получил решения, но мне понравилась идея, поэтому я закончил кастингом вроде.mapToObj(c -> Character.toString((char) c))
Вишва Ратна
3
Я бы предпочелIntStream.rangeClosed('a', 'z').flatMap(c -> IntStream.of(c,Character.toUpperCase(c))) .mapToObj(c -> Character.toString((char)c)) .collect(Collectors.joining("<", "<", ""));
Хольгер
0

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

public static int compare(String s1, String s2)
{
    int len, i;
    if (s1.length()<s2.length()) {
        len = s1.length();
    } else {
        len = s2.length();
    }
    for (i=0;i<len;i++) {
        if (Character.toUpperCase(s1.charAt(i)) < Character.toUpperCase(s2.charAt(i))) {
            return -1;
        } else if (Character.toUpperCase(s1.charAt(i)) > Character.toUpperCase(s2.charAt(i))) {
            return 1;
        } else if (s1.charAt(i) < s2.charAt(i)) {
            return -1;
        } else if (s1.charAt(i) > s2.charAt(i)) {
            return 1;
        }
    }
    if (s1.length() < s2.length()) {
        return -1;
    } else if (s1.length() > s2.length()) {
        return 1;
    } else {
        return 0;
    }
}

Затем вы можете передать этот метод Stream.sorted.

dbush
источник