Supprimer les signes diacritiques (ń ń ň ñ ṅ ņ ṇ ṋ ṉ ̈ ɲ ƞ ᶇ ɳ ȵ) des caractères Unicode


88

Je regarde un algorithme qui peut mapper entre les caractères avec des signes diacritiques ( tilde , circonflexe , caret , tréma , caron ) et leur caractère "simple".

Par exemple:

ń  ǹ  ň  ñ    ņ        ̈  ɲ  ƞ  ɳ ȵ  --> n
á --> a
ä --> a
 --> a
 --> o

Etc.

  1. Je veux faire cela en Java, bien que je soupçonne que cela devrait être quelque chose d'Unicode-y et devrait être faisable raisonnablement facilement dans n'importe quel langage.

  2. Objectif: permettre de rechercher facilement des mots avec des signes diacritiques. Par exemple, si j'ai une base de données de joueurs de tennis et que Björn_Borg est entré, je conserverai également Bjorn_Borg afin que je puisse le trouver si quelqu'un entre à Bjorn et non à Björn.


Cela dépend de l'environnement dans lequel vous programmez, même si vous devrez probablement gérer manuellement une sorte de table de mappage. Alors, quelle langue utilisez-vous?
Thorarin

15
Veuillez noter que certaines lettres comme ñ en.wikipedia.org/wiki/%C3%91 ne doivent pas être supprimées de leurs signes diacritiques à des fins de recherche. Google différencie correctement l'espagnol "ano" (anus) et "año" (année). Donc, si vous voulez vraiment un bon moteur de recherche, vous ne pouvez pas compter sur la suppression de base des signes diacritiques.
Eduardo

@Eduardo: Dans un contexte donné, cela n'a peut-être pas d'importance. En utilisant l'exemple donné par le PO, en recherchant le nom d'une personne dans un contexte multinational, vous voulez en fait que la recherche ne soit pas trop précise.
Amir Abiri

(Envoyé accidentellement précédemment) Il est cependant possible de mapper les signes diacritiques à leurs équivalents phonétiques pour améliorer la recherche phonétique. ie ñ => ni donnera de meilleurs résultats si le moteur de recherche sous-jacent prend en charge la recherche phonétique (par exemple soundex)
Amir Abiri

Un cas d'utilisation où le changement de año en ano etc. supprime les caractères non base64 pour les URL, les identifiants, etc.
Ondra Žižka

Réponses:


82

J'ai fait cela récemment en Java:

public static final Pattern DIACRITICS_AND_FRIENDS
    = Pattern.compile("[\\p{InCombiningDiacriticalMarks}\\p{IsLm}\\p{IsSk}]+");

private static String stripDiacritics(String str) {
    str = Normalizer.normalize(str, Normalizer.Form.NFD);
    str = DIACRITICS_AND_FRIENDS.matcher(str).replaceAll("");
    return str;
}

Cela fera comme vous l'avez spécifié:

stripDiacritics("Björn")  = Bjorn

mais il échouera par exemple sur Białystok, car le łcaractère n'est pas diacritique.

Si vous voulez avoir un simplificateur de chaîne à part entière, vous aurez besoin d'un deuxième cycle de nettoyage, pour certains caractères plus spéciaux qui ne sont pas des signes diacritiques. Est-ce cette carte, j'ai inclus les caractères spéciaux les plus courants qui apparaissent dans les noms de nos clients. Ce n'est pas une liste complète, mais cela vous donnera une idée de la façon de l'étendre. ImmutableMap n'est qu'une simple classe de google-collections.

public class StringSimplifier {
    public static final char DEFAULT_REPLACE_CHAR = '-';
    public static final String DEFAULT_REPLACE = String.valueOf(DEFAULT_REPLACE_CHAR);
    private static final ImmutableMap<String, String> NONDIACRITICS = ImmutableMap.<String, String>builder()

        //Remove crap strings with no sematics
        .put(".", "")
        .put("\"", "")
        .put("'", "")

        //Keep relevant characters as seperation
        .put(" ", DEFAULT_REPLACE)
        .put("]", DEFAULT_REPLACE)
        .put("[", DEFAULT_REPLACE)
        .put(")", DEFAULT_REPLACE)
        .put("(", DEFAULT_REPLACE)
        .put("=", DEFAULT_REPLACE)
        .put("!", DEFAULT_REPLACE)
        .put("/", DEFAULT_REPLACE)
        .put("\\", DEFAULT_REPLACE)
        .put("&", DEFAULT_REPLACE)
        .put(",", DEFAULT_REPLACE)
        .put("?", DEFAULT_REPLACE)
        .put("°", DEFAULT_REPLACE) //Remove ?? is diacritic?
        .put("|", DEFAULT_REPLACE)
        .put("<", DEFAULT_REPLACE)
        .put(">", DEFAULT_REPLACE)
        .put(";", DEFAULT_REPLACE)
        .put(":", DEFAULT_REPLACE)
        .put("_", DEFAULT_REPLACE)
        .put("#", DEFAULT_REPLACE)
        .put("~", DEFAULT_REPLACE)
        .put("+", DEFAULT_REPLACE)
        .put("*", DEFAULT_REPLACE)

        //Replace non-diacritics as their equivalent characters
        .put("\u0141", "l") // BiaLystock
        .put("\u0142", "l") // Bialystock
        .put("ß", "ss")
        .put("æ", "ae")
        .put("ø", "o")
        .put("©", "c")
        .put("\u00D0", "d") // All Ð ð from http://de.wikipedia.org/wiki/%C3%90
        .put("\u00F0", "d")
        .put("\u0110", "d")
        .put("\u0111", "d")
        .put("\u0189", "d")
        .put("\u0256", "d")
        .put("\u00DE", "th") // thorn Þ
        .put("\u00FE", "th") // thorn þ
        .build();


    public static String simplifiedString(String orig) {
        String str = orig;
        if (str == null) {
            return null;
        }
        str = stripDiacritics(str);
        str = stripNonDiacritics(str);
        if (str.length() == 0) {
            // Ugly special case to work around non-existing empty strings
            // in Oracle. Store original crapstring as simplified.
            // It would return an empty string if Oracle could store it.
            return orig;
        }
        return str.toLowerCase();
    }

    private static String stripNonDiacritics(String orig) {
        StringBuffer ret = new StringBuffer();
        String lastchar = null;
        for (int i = 0; i < orig.length(); i++) {
            String source = orig.substring(i, i + 1);
            String replace = NONDIACRITICS.get(source);
            String toReplace = replace == null ? String.valueOf(source) : replace;
            if (DEFAULT_REPLACE.equals(lastchar) && DEFAULT_REPLACE.equals(toReplace)) {
                toReplace = "";
            } else {
                lastchar = toReplace;
            }
            ret.append(toReplace);
        }
        if (ret.length() > 0 && DEFAULT_REPLACE_CHAR == ret.charAt(ret.length() - 1)) {
            ret.deleteCharAt(ret.length() - 1);
        }
        return ret.toString();
    }

    /*
    Special regular expression character ranges relevant for simplification -> see http://docstore.mik.ua/orelly/perl/prog3/ch05_04.htm
    InCombiningDiacriticalMarks: special marks that are part of "normal" ä, ö, î etc..
        IsSk: Symbol, Modifier see http://www.fileformat.info/info/unicode/category/Sk/list.htm
        IsLm: Letter, Modifier see http://www.fileformat.info/info/unicode/category/Lm/list.htm
     */
    public static final Pattern DIACRITICS_AND_FRIENDS
        = Pattern.compile("[\\p{InCombiningDiacriticalMarks}\\p{IsLm}\\p{IsSk}]+");


    private static String stripDiacritics(String str) {
        str = Normalizer.normalize(str, Normalizer.Form.NFD);
        str = DIACRITICS_AND_FRIENDS.matcher(str).replaceAll("");
        return str;
    }
}

qu'en est-il des personnages comme ╨?
mickthompson

ils seront ignorés. de même tous les caractères japonais etc.
Andreas Petersson

merci Andreas. Existe-t-il un moyen de les supprimer? Des caractères comme ら が な を 覚 男 (ou autres) seront inclus dans la chaîne générée et ceux-ci casseront fondamentalement la sortie. J'essaie d'utiliser la sortie simplifiedString comme générateur d'URL comme le fait StackOverflow pour les URL de ses questions.
mickthompson

2
Comme je l'ai dit dans le commentaire de la question. Vous ne pouvez pas compter sur la suppression de base des signes diacritiques si vous voulez un bon moteur de recherche.
Eduardo

3
Merci Andreas, fonctionne comme un charme! (testé sur rrrr̈r'ŕřttẗţỳỹẙy'yýÿŷpp̈sss̈s̊s's̸śŝŞşšddd̈ďd'ḑf̈f̸ggg̈g'ģqĝǧḧĥj̈j'ḱkk̈k̸ǩlll̈Łłẅẍcc̈c̊c'c̸Çççćĉčvv̈v'v̸bb̧ǹnn̈n̊n'ńņňñmmmm̈m̊m̌ǵß) :-)
Fortega

24

Le package principal java.text a été conçu pour répondre à ce cas d'utilisation (correspondance de chaînes sans se soucier des signes diacritiques, de la casse, etc.).

Configurez a Collatorpour trier les PRIMARYdifférences de caractères. Avec cela, créez un CollationKeypour chaque chaîne. Si tout votre code est en Java, vous pouvez utiliser CollationKeydirectement. Si vous devez stocker les clés dans une base de données ou un autre type d'index, vous pouvez le convertir en un tableau d'octets .

Ces classes utilisent les données de pliage de casse standard Unicode pour déterminer quels caractères sont équivalents et prennent en charge diverses stratégies de décomposition .

Collator c = Collator.getInstance();
c.setStrength(Collator.PRIMARY);
Map<CollationKey, String> dictionary = new TreeMap<CollationKey, String>();
dictionary.put(c.getCollationKey("Björn"), "Björn");
...
CollationKey query = c.getCollationKey("bjorn");
System.out.println(dictionary.get(query)); // --> "Björn"

Notez que les assembleurs sont spécifiques aux paramètres régionaux. Cela est dû au fait que «l'ordre alphabétique» diffère entre les paramètres régionaux (et même dans le temps, comme cela a été le cas avec l'espagnol). La Collatorclasse que vous soulage d'avoir à suivre toutes ces règles et de les tenir à jour.


semble intéressant, mais pouvez-vous rechercher votre clé de classement dans la base de données avec select * from person où collated_name comme 'bjo%' ??
Andreas Petersson

très gentil, je ne savais pas à ce sujet. va essayer ceci.
Andreas Petersson

Sur Android, les CollationKeys ne peuvent pas être utilisés comme préfixes pour les recherches dans la base de données. Une clé de classement de la chaîne ase transforme en octets 41, 1, 5, 1, 5, 0, mais la chaîne abse transforme en octets 41, 43, 1, 6, 1, 6, 0. Ces séquences d'octets n'apparaissent pas telles quelles en mots entiers (le tableau d'octets pour la clé de classement an'apparaît pas dans le tableau d'octets pour la clé de classement pour ab)
Grzegorz Adam Hankiewicz

1
@GrzegorzAdamHankiewicz Après quelques tests, je vois que les tableaux d'octets peuvent être comparés, mais ne forment pas de préfixes, comme vous l'avez noté. Donc, pour faire une requête de préfixe comme bjo%, vous devez effectuer une requête de plage où les collators sont> = bjoet < bjp(ou quel que soit le symbole suivant dans cette locale, et il n'y a pas de moyen par programme de le déterminer).
erickson

16

Il fait partie d' Apache Commons Lang à partir de la ver. 3.1.

org.apache.commons.lang3.StringUtils.stripAccents("Añ");

Retour An


1
Pour Ø ça redonne Ø
Mike Argyriou

2
Merci Mike de l'avoir signalé. La méthode ne gère que les accents. Le résultat de "ń ǹ ň ñ ṅ ņ ṇ ṋ ṉ ̈ ɲ ƞ ᶇ ɳ ȵ" est "nnnnnnnnn ɲ ƞ ᶇ ɳ ȵ"
Kenston Choi

12

Vous pouvez utiliser la classe Normalizer à partir de java.text:

System.out.println(new String(Normalizer.normalize("ń ǹ ň ñ ṅ ņ ṇ ṋ", Normalizer.Form.NFKD).getBytes("ascii"), "ascii"));

Mais il y a encore du travail à faire, puisque Java fait des choses étranges avec des caractères Unicode non convertibles (il ne les ignore pas, et ne lève pas d'exception). Mais je pense que vous pourriez vous en servir comme point de départ.


3
cela ne fonctionnera pas pour les signes diacritiques non-ascii, comme en russe, ils ont aussi des signes diacritiques, et en plus boucheront toutes les chaînes asiatiques. ne pas utiliser. au lieu de convertir en ascii, utilisez \\ p {InCombiningDiacriticalMarks} regexp comme dans la réponse stackoverflow.com/questions/1453171/...
Andreas Petersson


4

Veuillez noter que toutes ces marques ne sont pas simplement des "marques" sur un caractère "normal", que vous pouvez supprimer sans en changer la signification.

En suédois, å ä et ö sont de vrais et propres caractères de première classe, et non une «variante» d'un autre caractère. Ils sonnent différemment de tous les autres caractères, ils se trient différemment et ils font changer le sens des mots («mätt» et «matt» sont deux mots différents).


4
Bien que correct, il s'agit plus d'un commentaire que d'une réponse à la question.
Simon Forsberg

2

Unicode a des caractères diatriques spécifiques (qui sont des caractères composites) et une chaîne peut être convertie de sorte que le caractère et les diatriques soient séparés. Ensuite, vous pouvez simplement supprimer les diatricts de la chaîne et vous avez pratiquement terminé.

Pour plus d'informations sur la normalisation, les décompositions et l'équivalence, consultez la norme Unicode sur la page d'accueil Unicode .

Cependant, la manière dont vous pouvez réellement y parvenir dépend du framework / OS / ... sur lequel vous travaillez. Si vous utilisez .NET, vous pouvez utiliser la méthode String.Normalize en acceptant l' énumération System.Text.NormalizationForm .


2
C'est la méthode que j'utilise dans .NET, bien que je doive encore mapper certains caractères manuellement. Ce ne sont pas des signes diacritiques, mais des digraphes. Problème similaire cependant.
Thorarin

1
Convertir en forme de normalisation "D" (c'est-à-dire décomposé) et prendre le caractère de base.
Richard

2

Le moyen le plus simple (pour moi) serait simplement de maintenir un tableau de mappage épars qui change simplement vos points de code Unicode en chaînes affichables.

Tel que:

start    = 0x00C0
size     = 23
mappings = {
    "A","A","A","A","A","A","AE","C",
    "E","E","E","E","I","I","I", "I",
    "D","N","O","O","O","O","O"
}
start    = 0x00D8
size     = 6
mappings = {
    "O","U","U","U","U","Y"
}
start    = 0x00E0
size     = 23
mappings = {
    "a","a","a","a","a","a","ae","c",
    "e","e","e","e","i","i","i", "i",
    "d","n","o","o","o","o","o"
}
start    = 0x00F8
size     = 6
mappings = {
    "o","u","u","u","u","y"
}
: : :

L'utilisation d'une rare gamme vous permettra de représenter efficacement les remplacements même lorsqu'ils en sections largement espacées de la table Unicode. Les remplacements de chaînes permettront à des séquences arbitraires de remplacer vos signes diacritiques (comme le ægraphème en devenir ae).

C'est une réponse indépendante de la langue, donc, si vous avez une langue spécifique à l'esprit, il y aura de meilleures façons (même si elles reviendront probablement toutes à cela aux niveaux les plus bas de toute façon).


Ajouter tous les personnages étranges possibles n'est pas une tâche facile. Lorsque vous faites cela pour seulement quelques caractères, c'est une bonne solution.
Simon Forsberg

2

Quelque chose à considérer: si vous prenez la route d'essayer d'obtenir une seule «traduction» de chaque mot, vous risquez de manquer des alternatives possibles.

Par exemple, en allemand, lors du remplacement du "s-set", certaines personnes peuvent utiliser "B", tandis que d'autres peuvent utiliser "ss". Ou, en remplaçant un o umlauted par "o" ou "oe". Je pense que toute solution que vous proposez devrait, idéalement, inclure les deux.


2

Dans Windows et .NET, je convertis simplement en utilisant l'encodage de chaîne. De cette façon, j'évite le mappage et le codage manuels.

Essayez de jouer avec l'encodage de chaînes.


3
Pouvez-vous élaborer sur l'encodage de chaînes? Par exemple, avec un exemple de code.
Peter Mortensen

2

Dans le cas de l'allemand, il n'est pas nécessaire de supprimer les signes diacritiques des trémas (ä, ö, ü). Au lieu de cela, ils sont remplacés par une combinaison de deux lettres (ae, oe, ue) Par exemple, Björn doit être écrit comme Bjoern (et non Bjorn) pour avoir une prononciation correcte.

Pour cela, j'aurais plutôt un mappage codé en dur, où vous pouvez définir la règle de remplacement individuellement pour chaque groupe de caractères spéciaux.


0

Pour référence future, voici une méthode d'extension C # qui supprime les accents.

public static class StringExtensions
{
    public static string RemoveDiacritics(this string str)
    {
        return new string(
            str.Normalize(NormalizationForm.FormD)
                .Where(c => CharUnicodeInfo.GetUnicodeCategory(c) != 
                            UnicodeCategory.NonSpacingMark)
                .ToArray());
    }
}
static void Main()
{
    var input = "ŃŅŇ ÀÁÂÃÄÅ ŢŤţť Ĥĥ àáâãäå ńņň";
    var output = input.RemoveDiacritics();
    Debug.Assert(output == "NNN AAAAAA TTtt Hh aaaaaa nnn");
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.