Je regarde un algorithme qui peut mapper entre des caractères avec des signes diacritiques ( tilde , circumflex , caret , tréma , caron ) et leur caractère "simple".
Par exemple:
ń ǹ ň ñ ṅ ņ ṇ ṋ ṉ ̈ ɲ ƞ ᶇ ɳ ȵ --> n
á --> a
ä --> a
ấ --> a
ṏ --> o
Etc.
Je veux le faire en Java, bien que je pense que cela devrait être quelque chose d'Unicode-y et devrait être faisable assez facilement dans n'importe quelle langue.
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 garderai également Bjorn_Borg afin que je puisse le trouver si quelqu'un entre dans Bjorn et non Björn.
Je l'ai fait 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 ł
le caractère n'est pas diacritique.
Si vous voulez avoir un simplificateur de chaîne complet, vous aurez besoin d'un deuxième cycle de nettoyage, pour certains caractères spéciaux qui ne sont pas des signes diacritiques. Est cette carte, j'ai inclus les caractères spéciaux les plus courants qui apparaissent dans nos noms de clients. Ce n'est pas une liste complète, mais cela vous donnera une idée de comment l'étendre. Le immutableMap est juste une classe simple 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;
}
}
Le package Java.text de base a été conçu pour répondre à ce cas d'utilisation (faire correspondre les chaînes sans se soucier des signes diacritiques, de la casse, etc.).
Configurez un Collator
pour trier les différences de caractères PRIMARY
. Avec cela, créez un CollationKey
pour chaque chaîne. Si tout votre code est en Java, vous pouvez utiliser directement le CollationKey
. Si vous avez besoin de 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 repliement de la casse standard Unicode pour déterminer quels caractères sont équivalents et prennent en charge diverses stratégies 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 classeurs sont spécifiques aux paramètres régionaux. En effet, "l'ordre alphabétique" diffère selon les régions (et même au fil du temps, comme cela a été le cas avec l'espagnol). La classe Collator
vous dispense de suivre toutes ces règles et de les tenir à jour.
Cela fait partie de Apache Commons Lang à partir du ver. 3.1.
org.Apache.commons.lang3.StringUtils.stripAccents("Añ");
renvoie An
Vous pouvez utiliser classe Normalizer from Java.text
:
System.out.println(new String(Normalizer.normalize("ń ǹ ň ñ ṅ ņ ṇ ṋ", Normalizer.Form.NFKD).getBytes("ascii"), "ascii"));
Mais il y a encore du travail à faire, car 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 utiliser cela comme point de départ.
Il y a projet de rapport sur le pliage de caractères sur le site Web Unicode qui contient beaucoup de matériel pertinent. Voir spécifiquement la section 4.1. "Algorithme de pliage".
Voici un discussion et implémentation de suppression du marqueur diacritique en utilisant Perl.
Ces SO questions existantes sont liées:
Veuillez noter que toutes ces marques ne sont pas simplement des "marques" sur un caractère "normal", que vous pouvez supprimer sans changer la signification.
En suédois, å ä et ö sont des caractères véritables et propres de première classe, pas une "variante" d'un autre caractère. Ils sonnent différemment de tous les autres caractères, ils sont différents et font changer le sens des mots ("mätt" et "matt" sont deux mots différents).
Sous Windows et .NET, je convertis simplement en utilisant l'encodage de chaînes. De cette façon, j'évite le mappage et le codage manuels.
Essayez de jouer avec l'encodage des chaînes.
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 (pas Bjorn) pour avoir une prononciation correcte.
Pour cela, j'aurais plutôt une cartographie codée en dur, où vous pouvez définir la règle de remplacement individuellement pour chaque groupe de caractères spéciaux.
Quelque chose à considérer: si vous allez dans la voie d'essayer d'obtenir une seule "traduction" de chaque mot, vous risquez de manquer quelques alternatives possibles.
Par exemple, en allemand, lors du remplacement du "s-set", certaines personnes pourraient utiliser "B", tandis que d'autres pourraient utiliser "ss". Ou, en remplaçant un o sous majuscule par "o" ou "oe". Je pense que toute solution que vous proposez devrait idéalement inclure les deux.
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 essentiellement terminé.
Pour plus d'informations sur la normalisation, les décompositions et l'équivalence, voir The Unicode Standard sur la page d'accueil Unicode .
Cependant, la façon 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 acceptant l'énumération System.Text.NormalizationForm .
Le moyen le plus simple (pour moi) serait de simplement maintenir un tableau de mappage clairsemé 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'un tableau éparse vous permettra de représenter efficacement les remplacements même lorsqu'ils se trouvent dans des 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 devenant 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 se résumeront probablement toutes au niveau le plus bas).
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");
}