Sortowanie niestandardowe w taki sposób, że A pojawia się przed a, a B przed b

11

Mam listę takich kolorów:

Różowy, niebieski, czerwony, niebieski, szary, zielony, fioletowy, czarny ... itp

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

Istnieje kilka operacji pośrednich, takich jak filtrowanie niektórych kolorów owoców, teraz mam filtrowane wyniki tam, gdzie chcę je posortować:

Niebieski, czarny, niebieski, szary, zielony, różowy, fioletowy, czerwony

Próbowałem :

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

Nie działa zgodnie z oczekiwaniami.

Dane wyjściowe są następujące:

czarny, niebieski, niebieski, zielony, szary, różowy, fioletowy, czerwony

Chcę:

Niebieski, czarny, niebieski, szary, zielony, różowy, fioletowy, czerwony

Vishwa Ratna
źródło
2
Czy czarne nie powinny pojawić się przed niebieskim, a zielone przed szarym?
Ravindra Ranwala
3
ajest wcześniej, uwięc wynik jest poprawny
Jens
2
@RavindraRanwala, Niebieski „s B jest kapitał, ale z powrotem ” s b nie jest.
Vishwa Ratna
2
Próbowałem, ale nie daje tej konkretnej kolejności. Daje [black, Blue, blue, green, Grey, Pink, purple, Red]@ chrylis-onstrike-
Ravindra Ranwala
2
Jeśli chcesz, aby obudowa była umieszczana przed obudową niższą, ignorowanie obudowy jest ostatnią rzeczą, którą chcesz.
Teepeemm

Odpowiedzi:

8

Moim rozwiązaniem jest użycie sortowania w dwóch krokach za pomocą Comparator.thenComparing()metody.

Najpierw porównaj ciągi tylko przez pierwszy znak, ignorując wielkość liter. Grupy z tą samą pierwszą postacią (bez względu na przypadek) pozostają do tej pory nieposortowane. Następnie w drugim kroku zastosuj normalne sortowanie alfabetyczne, aby posortować te nieposortowane podgrupy.

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);

Być może można go jeszcze zoptymalizować, ale daje pożądany rezultat:

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

DanielBK
źródło
Dokonano edycji pod kątem czytelności Comparator. Ale tak, zakłada to jedynie porównanie pierwszego znaku Ciągu, o którym OP również nie podkreślił zbyt wiele.
Naman
8

Możesz użyć RuleBasedCollator do zdefiniowania własnych reguł.

Przykład niestandardowej reguły:

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

Powyższa reguła jest dekodowana, ponieważ zarówno wielkie, jak i małe litery Cpowinny pojawiać się przed dużymi i małymi literami Bpodczas porównywania ciągów.

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);

Wynik:

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

Edycja: Zamiast pisać customRulesręcznie, możesz użyć poniższego kodu, aby go wygenerować.

String a = IntStream.range('a', 'z' + 1).mapToObj(c -> Character.toString((char) c))
        .flatMap(ch -> Stream
            .of("<", ch.toUpperCase(), "<", ch)).collect(Collectors.joining(""));
Vishwa Ratna
źródło
2
stworzenie String customRulesmoże być automatyzmem z IntStream:IntStream.range('a', 'z' + 1) .mapToObj(Character::toString) .flatMap(ch -> Stream.of("<", ch.toUpperCase(), "<", ch)) .collect(Collectors.joining(""))
Łczapskim
@ Łapapski, jakoś .mapToObj(Character::toString)nie zostanie rozwiązany, myślę, że musisz użyć.mapToObj(c -> Character.toString((char) c))
Vishwa Ratna
mapToObj(Character::toString)działa tylko w Javie 11 lub nowszej.
Holger
@Holger Ok, próbowałem na moim systemie (JDK-8) i mapToObj(Character::toString)nie byłem rozwiązany, ale spodobał mi się ten pomysł, więc skończyłem na castingu jak.mapToObj(c -> Character.toString((char) c))
Vishwa Ratna
3
WolałbymIntStream.rangeClosed('a', 'z').flatMap(c -> IntStream.of(c,Character.toUpperCase(c))) .mapToObj(c -> Character.toString((char)c)) .collect(Collectors.joining("<", "<", ""));
Holger
0

Potrzebujesz metody, która najpierw rozróżnia wielkość liter dla każdej litery, a następnie, jeśli istnieje dopasowanie, wykonaj porównanie wielkości liter dla każdej litery:

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;
    }
}

Następnie możesz przekazać tę metodę do Stream.sorted.

dbush
źródło