Tri personnalisé de manière à ce que A précède a et B précède b

11

J'ai une liste de couleurs comme celle-ci:

Rose, bleu, rouge, bleu, gris, vert, violet, noir ... etc

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

Il y a des opérations intermédiaires comme le filtrage de certaines couleurs de fruits, maintenant je reste avec des résultats filtrés où je veux qu'ils soient triés dans l'ordre:

Bleu, noir, bleu, gris, vert, rose, violet, rouge

J'ai essayé :

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

Cela ne fonctionne pas comme prévu.

La sortie est la suivante:

noir, bleu, bleu, vert, gris, rose, violet, rouge

Je veux ce qui suit:

Bleu, noir, bleu, gris, vert, rose, violet, rouge

Vishwa Ratna
la source
2
Le noir ne doit-il pas passer avant le bleu et le vert avant le gris?
Ravindra Ranwala
3
aest avant udonc le résultat est correct
Jens
2
@RavindraRanwala, le B de Blue est le capital mais le b de Back ne l'est pas.
Vishwa Ratna
2
J'ai essayé, mais ça ne donne pas cet ordre précis. Cela donne [black, Blue, blue, green, Grey, Pink, purple, Red]@ chrylis-onstrike-
Ravindra Ranwala
2
Si vous voulez que les majuscules viennent avant les minuscules, ignorer les majuscules est la dernière chose que vous voulez.
Teepeemm

Réponses:

8

Ma solution est d'utiliser le tri en deux étapes en utilisant la Comparator.thenComparing()méthode.

Tout d'abord, comparez les chaînes uniquement par le premier caractère en ignorant la casse. Les groupes avec le même premier caractère (quel que soit le cas) restent donc non triés jusqu'à présent. Ensuite, dans la deuxième étape, appliquez le tri alphabétique normal pour trier ces sous-groupes non triés.

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

Peut-être qu'il peut encore être optimisé, mais il donne le résultat souhaité:

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

DanielBK
la source
A fait un montage pour la lisibilité de Comparator. Mais oui, cela suppose de ne comparer que le premier caractère de la chaîne sur lequel l'OP n'a pas beaucoup insisté non plus.
Naman
8

Vous pouvez utiliser RuleBasedCollator pour définir vos propres règles.

Exemple de règle personnalisée :

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

La règle ci-dessus est décodée car les majuscules et les minuscules Cdoivent apparaître avant les majuscules et les minuscules Blors de la comparaison des chaînes.

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

Production:

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

Modifier: Au lieu d'écrire à la customRulesmain, vous pouvez utiliser le code ci-dessous pour le générer.

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
la source
2
la création String customRulespeut être de l'automatisme avec IntStream:IntStream.range('a', 'z' + 1) .mapToObj(Character::toString) .flatMap(ch -> Stream.of("<", ch.toUpperCase(), "<", ch)) .collect(Collectors.joining(""))
lczapski
@lczapski, d'une manière ou d'une autre .mapToObj(Character::toString)ne sera pas résolu, je suppose que vous devez utiliser.mapToObj(c -> Character.toString((char) c))
Vishwa Ratna
mapToObj(Character::toString)ne fonctionne que dans Java 11 ou plus récent.
Holger
@Holger Ok, j'ai essayé mon système (JDK-8) et mapToObj(Character::toString)je n'étais pas résolu, mais j'ai aimé l'idée, donc je finis par faire du casting comme.mapToObj(c -> Character.toString((char) c))
Vishwa Ratna
3
Je préfèreIntStream.rangeClosed('a', 'z').flatMap(c -> IntStream.of(c,Character.toUpperCase(c))) .mapToObj(c -> Character.toString((char)c)) .collect(Collectors.joining("<", "<", ""));
Holger
0

Vous avez besoin d'une méthode qui effectue d'abord une comparaison insensible à la casse sur chaque lettre, puis s'il y a une correspondance, effectuez une comparaison sensible à la casse sur chaque lettre:

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

Vous pouvez ensuite transmettre cette méthode à Stream.sorted.

dbush
la source