Clasificación personalizada en la forma en que A viene antes de a y B viene antes de b

11

Tengo una lista de colores como esta:

Rosa, azul, rojo, azul, gris, verde, morado, negro ... etc.

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

Hay algunas operaciones intermedias como filtrar algunos colores de frutas, ahora me quedo con los resultados filtrados donde quiero que se ordenen en orden:

Azul, negro, azul, gris, verde, rosa, morado, rojo

Yo he tratado :

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

No funciona como se esperaba.

El resultado es el siguiente:

negro, azul, azul, verde, gris, rosa, morado, rojo

Quiero lo siguiente:

Azul, negro, azul, gris, verde, rosa, morado, rojo

Vishwa Ratna
fuente
2
¿No debería el negro venir antes que el azul y el verde antes que el gris?
Ravindra Ranwala
3
aes antes, uasí que el resultado es correcto
Jens
2
@RavindraRanwala, la B de Blue es mayúscula pero la b de Back no lo es.
Vishwa Ratna
2
Lo intenté, pero no da este orden específico. Da [black, Blue, blue, green, Grey, Pink, purple, Red]@ chrylis-onstrike-
Ravindra Ranwala
2
Si desea que la carcasa de capital venga antes que la carcasa inferior, ignorar la carcasa es lo último que desea.
Teepeemm

Respuestas:

8

Mi solución es usar la clasificación en dos pasos usando el Comparator.thenComparing()método

Primero, compare las cadenas solo por el primer carácter ignorando mayúsculas y minúsculas. Por lo tanto, los grupos con el mismo primer carácter (sin importar el caso) permanecen sin clasificar hasta ahora. Luego, en el segundo paso, aplique la ordenación alfabética normal para ordenar los subgrupos sin clasificar.

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

Quizás todavía se puede optimizar, pero da el resultado deseado:

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

DanielBK
fuente
Hizo una edición para la legibilidad de Comparator. Pero sí, esto supone solo comparar el primer carácter de la Cadena sobre el cual el OP tampoco ha enfatizado demasiado.
Naman
8

Puede usar RuleBasedCollator para definir sus propias Reglas.

Ejemplo de regla personalizada :

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

La regla anterior se decodifica para que las mayúsculas y minúsculas Caparezcan antes de las mayúsculas y minúsculas Bal comparar cadenas.

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

Salida:

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

Editar: en lugar de escribirlo a customRulesmano, puede usar el siguiente código para generarlo.

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
fuente
2
la creación String customRulespuede ser automatismo con IntStream:IntStream.range('a', 'z' + 1) .mapToObj(Character::toString) .flatMap(ch -> Stream.of("<", ch.toUpperCase(), "<", ch)) .collect(Collectors.joining(""))
lczapski
@lczapski, de alguna manera .mapToObj(Character::toString)no se resolverá, supongo que debes usar.mapToObj(c -> Character.toString((char) c))
Vishwa Ratna
mapToObj(Character::toString)solo funciona en Java 11 o más reciente.
Holger
@Holger Ok, probé en mi Sistema (JDK-8) y mapToObj(Character::toString)no me estaba resolviendo, pero me gustó la idea, así que termino haciendo casting como.mapToObj(c -> Character.toString((char) c))
Vishwa Ratna
3
PrefieroIntStream.rangeClosed('a', 'z').flatMap(c -> IntStream.of(c,Character.toUpperCase(c))) .mapToObj(c -> Character.toString((char)c)) .collect(Collectors.joining("<", "<", ""));
Holger
0

Necesita un método que primero haga una comparación entre mayúsculas y minúsculas en cada letra, luego, si hay una coincidencia, realice una comparación entre mayúsculas y minúsculas en 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;
    }
}

Luego puede pasar este método a Stream.sorted.

dbush
fuente