9
votes

java.text.Collator traite "v" et "w" comme la même lettre pour la langue / locale suédoise

Le test suivant réussit correctement avec Java 8.

Comparator<String> stringComparator = Collator.getInstance(new Locale("sv", "SE"));

Assert.assertTrue(stringComparator.compare("aaaa", "bbbb") < 0);
Assert.assertTrue(stringComparator.compare("waaa", "vbbb") < 0);
Assert.assertTrue(stringComparator.compare("vaaa", "wbbb") < 0);

Ceci commande waaa avant vbbb et vaaa code > avant wbbb . Apparemment, il traite v et w comme la même lettre.

En fait, selon Wikipedia, en langue suédoise:

En 2006, l'utilisation de "W" avait augmenté en raison des nouveaux mots d'emprunt. "W" est donc officiellement devenu une lettre et la règle de tri "V" = "W" est devenue obsolète. Les livres et logiciels antérieurs à 2006 utilisent généralement la règle. Une fois la règle obsolète, certains livres et logiciels ont continué de l'appliquer.

Quelqu'un a-t-il une solution générale à ce problème, de sorte que v et w soient traités comme des lettres séparées dans les paramètres régionaux suédois?

p>


5 commentaires

Bizarre! Je peux confirmer que c'est toujours le cas dans les bibliothèques Java 10.


Peut docs.oracle.com/javase/tutorial/i18n/text/rule .html d'aide?


Êtes-vous sûr d'utiliser le java.text.Collator standard? Comme ce type a une méthode getInstance plutôt que newInstance et implémente Comparator plutôt que Comparator


Je ne vois pas cette déclaration citée sur Wikipedia ( sv.wikipedia.org/wiki/Svenska_alfabetet ). Tout ce que je peux voir, c'est qu'un dictionnaire spécifique a utilisé une différenciation entre W et V pour son glossaire alors que les mêmes auteurs recommandent toujours d'utiliser les anciennes règles pour les noms suédois. Aucun des deux ordres n'est faux. De plus, il ne faut jamais affirmer que le résultat d'un comparateur est précisément +1 ou -1 , tout ce qui compte, c'est le signe du résultat.


@Holger merci pour vos remarques, c'est bien getInstance . Je modifierai également les tests.


3 Réponses :


4
votes

Créez votre propre RuleBasedCollator .

Vérifiez la valeur de la chaîne renvoyée par

((RuleBasedCollator)Collator.getInstance(new Locale("sv", "SE"))).getRules()

et modifiez-la en fonction de vos besoins, puis créez un nouvel assembleur avec vos règles modifiées.

Et probablement aussi soumettre un rapport de bogue JDK, pour faire bonne mesure.


0 commentaires

1
votes

Ceci commande waaa avant vbbb et vaaa avant wbbb. Apparemment il traite v et w comme la même lettre.

JDK ne traite en effet pas «w» et «v» comme les mêmes caractères, même dans les paramètres régionaux suédois. La lettre «v» vient avant «w».

Assert.assertEquals (1, stringComparator.compare ("w", "v")); // TRUE

Cependant, selon les règles de classement suédoises, JDK ordonne «wa» avant «vb».

Assert.assertEquals (1, stringComparator.compare ("wa", "vb")); // FALSE


3 commentaires

Comme cela ne résout pas le problème, il serait plus approprié de commenter la question.


@Lii, cela résout en quelque sorte le problème d'OP en démontrant avec un exemple de code que ce n'est pas un bogue dans JDK qui fait la différence entre v et w pour la locale suédoise.


Il les traite comme des personnages différents, mais seulement d'une différence secondaire.



1
votes

Vous pouvez créer un comparateur personnalisé, qui encapsule l'assembleur et gère manuellement v et w comme vous le souhaitez.

J'en ai fait deux implémentations.

Le premier est court et élégant, il utilise des goyaves comparateur lexicographique avec le regex délicat fourni par Holger dans un commentaire.

public static void main(String[] args) throws Exception {
    Comparator<String> stringComparator = correctVwWrapper(Collator.getInstance(new Locale("sv", "SE")));

    System.out.println(stringComparator.compare("a", "z") < 0);     // true
    System.out.println(stringComparator.compare("wa", "vz") < 0);   // false
    System.out.println(stringComparator.compare("wwa", "vvz") < 0); // false
    System.out.println(stringComparator.compare("va", "wz") < 0);   // true
    System.out.println(stringComparator.compare("v", "w") < 0);     // true
}

La deuxième implémentation est une chose grande et complexe qui fait la même chose, mais implémentée manuellement, sans bibliothèques ni expressions régulières.

public static Comparator<String> correctVwWrapper(Comparator<Object> original) {
    return (s1, s2) -> compareSplittedVw(original, s1, s2);
}

/**
 * Compares the two string by first splitting them into segments separated by W
 * and V, then comparing the segments one by one.
 */
private static int compareSplittedVw(Comparator<Object> original, String s1, String s2) {
    List<String> l1 = splitVw(s1);
    List<String> l2 = splitVw(s2);

    int minSize = Math.min(l1.size(), l2.size());

    for (int ix = 0; ix < minSize; ix++) {
        int comp = original.compare(l1.get(ix), l2.get(ix));
        if (comp != 0) {
            return comp; 
        }
    }

    return Integer.compare(l1.size(), l2.size());
}

private static boolean isVw(int ch) {
    return ch == 'V' || ch == 'v' || ch == 'W' || ch == 'w';
}


/**
 * Splits the string into segments separated by V and W.
 */
public static List<String> splitVw(String s) {
    var b = new StringBuilder();

    var result = new ArrayList<String>();

    for (int offset = 0; offset < s.length();) {
        int ch = s.codePointAt(offset);

        if (isVw(ch)) {
            if (b.length() > 0) {
                result.add(b.toString());
                b.setLength(0);
            }

            result.add(Character.toString((char) ch));
        } else {
            b.appendCodePoint(ch);
        }

        offset += Character.charCount(ch);
    }

    if (b.length() > 0) {
        result.add(b.toString());
    }

    return result;
}

Utilisation:

private static final Pattern VW_BOUNDARY = Pattern.compile("(?=[vw])|(?<=[vw])", Pattern.CASE_INSENSITIVE);

public static Comparator<String> smallCorrectVwWrapper(Comparator<Object> original) {
    return Comparator.comparing(
        s -> Arrays.asList(VW_BOUNDARY.split((String) s)),
        Comparators.lexicographical(original));

Il faut un peu plus de travail pour implémenter un Collator wrapping, mais cela ne devrait pas être trop compliqué. P >


4 commentaires

C'est très bien pour cette poignée de chaînes de test, mais que se passe-t-il si v ou w apparaît à un emplacement différent du premier?


@Holger: Oh. Vous avez raison. Je ne sais pas d'où j'ai eu l'idée que seul le premier caractère compte. Je vais essayer de trouver une solution, sinon je supprimerai la réponse.


@Holger: J'ai ajouté une deuxième tentative d'implémentation de la logique de correction. La complexité de cela me fait penser que la solution de DodgyCodeException est peut-être assez bonne, même si vous devez en apprendre davantage sur la syntaxe de règle de classement étrange ...


Ouais, c’est vraiment complexe. Notez que vous pouvez simplement diviser en appelant split ("(? = [VVwV]) | (? <= [VVwW])") sur s1 et s2 . Ou, si vous souhaitez l'optimiser, préparez l'opération via static final Pattern VW_BOUNDARY = Pattern.compile ("(? = [Vw]) | (? <= [Vw])", Pattern.CASE_INSENSITIVE); < / code> et utilisez-le dans la méthode de comparaison via VW_BOUNDARY.split (s1) et VW_BOUNDARY.split (s2) .