Classificação personalizada da maneira que A vem antes de ae B vem antes de b

11

Eu tenho uma lista de cores como esta:

Rosa, azul, vermelho, azul, cinza, verde, roxo, preto ... etc

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

Existem algumas operações intermediárias, como filtrar algumas cores de frutas, agora tenho resultados filtrados onde quero que elas sejam classificadas em ordem:

Azul, preto, azul, cinza, verde, rosa, roxo, vermelho

Eu tentei :

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

Não funciona como esperado.

A saída é a seguinte:

preto, azul, azul, verde, cinza, rosa, roxo, vermelho

Eu quero o seguinte:

Azul, preto, azul, cinza, verde, rosa, roxo, vermelho

Vishwa Ratna
fonte
2
O preto não deveria vir antes do azul e verde antes do cinza?
Ravindra Ranwala 31/01
3
aé antes, uentão o resultado está correto
Jens
2
@RavindraRanwala, Blue 's B é capital, mas volta ' s b não é.
Vishwa Ratna 31/01
2
Eu tentei, mas não dá essa ordem específica. Dá [black, Blue, blue, green, Grey, Pink, purple, Red]@ chrylis-onstrike-
Ravindra Ranwala
2
Se você deseja que a caixa de capital chegue antes da caixa inferior, ignorar a caixa é a última coisa que você deseja.
Teepeemm 31/01

Respostas:

8

Minha solução é usar a classificação em duas etapas, usando o Comparator.thenComparing()método

Primeiro, compare as Strings apenas pelo primeiro caractere que ignora o caso. Portanto, os grupos com o mesmo primeiro caractere (não importa o caso) permanecem sem classificação até o momento. Em seguida, na segunda etapa, aplique a classificação alfabética normal para classificar os subgrupos não classificados.

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

Talvez ainda possa ser otimizado, mas fornece o resultado desejado:

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

DanielBK
fonte
Fez uma edição para facilitar a leitura Comparator. Mas sim, isso pressupõe apenas a comparação do primeiro caractere da String sobre o qual o OP também não enfatizou muito.
Naman 31/01
8

Você pode usar RuleBasedCollator para definir suas próprias regras.

Exemplo de regra personalizada :

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

A regra acima é decodificada, pois tanto as maiúsculas quanto as minúsculas Cdevem aparecer antes das maiúsculas e minúsculas Bao comparar seqüências de caracteres.

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

Resultado:

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

Editar: Em vez de escrever à customRulesmão, você pode usar o código abaixo para gerá-lo.

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
fonte
2
criação String customRulespode ser automatizada com IntStream:IntStream.range('a', 'z' + 1) .mapToObj(Character::toString) .flatMap(ch -> Stream.of("<", ch.toUpperCase(), "<", ch)) .collect(Collectors.joining(""))
lczapski 31/01
@lczapski, de alguma forma .mapToObj(Character::toString)não será resolvido, eu acho que você precisa usar.mapToObj(c -> Character.toString((char) c))
Vishwa Ratna 31/01
mapToObj(Character::toString)funciona apenas no Java 11 ou mais recente.
Holger
@ Holger Ok, eu tentei no meu sistema (JDK-8) e mapToObj(Character::toString)não estava sendo resolvido, mas gostei da idéia, então acabei fazendo casting como.mapToObj(c -> Character.toString((char) c))
Vishwa Ratna 31/01
3
Eu prefeririaIntStream.rangeClosed('a', 'z').flatMap(c -> IntStream.of(c,Character.toUpperCase(c))) .mapToObj(c -> Character.toString((char)c)) .collect(Collectors.joining("<", "<", ""));
Holger
0

Você precisa de um método que faça primeiro uma comparação sem distinção entre maiúsculas e minúsculas em cada letra e, se houver uma correspondência, faça uma comparação com distinção entre maiúsculas e minúsculas em cada letra:

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

Você pode então passar esse método para Stream.sorted.

dbush
fonte