Entfernen Sie diakritische Zeichen (ń ǹ char ñ ṅ ṅ ṇ ṋ ṉ ̈ ɲ ƞ ᶇ ɳ from) von Unicode-Zeichen


88

Ich betrachte einen Algorithmus, der Zeichen mit diakritischen Zeichen ( Tilde , Zirkumflex , Caret , Umlaut , Caron ) und deren "einfachem" Zeichen abbilden kann .

Beispielsweise:

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

Etc.

  1. Ich möchte dies in Java tun, obwohl ich vermute, dass es etwas Unicode-y sein sollte und in jeder Sprache relativ einfach machbar sein sollte.

  2. Zweck: Ermöglicht die einfache Suche nach Wörtern mit diakritischen Zeichen. Wenn ich beispielsweise eine Datenbank mit Tennisspielern habe und Björn_Borg eingegeben ist, behalte ich auch Björn_Borg, damit ich es finden kann, wenn jemand Björn und nicht Björn betritt.


Dies hängt davon ab, in welcher Umgebung Sie programmieren, obwohl Sie wahrscheinlich eine Art Zuordnungstabelle manuell verwalten müssen. Also, welche Sprache benutzt du?
Thorarin

15
Bitte beachten Sie, dass einige Buchstaben wie ñ en.wikipedia.org/wiki/%C3%91 nicht zu Suchzwecken entfernt werden sollten. Google unterscheidet korrekt zwischen Spanisch "ano" (Anus) und "año" (Jahr). Wenn Sie also wirklich eine gute Suchmaschine wollen, können Sie sich nicht auf die grundlegende Entfernung diakritischer Markierungen verlassen.
Eduardo

@Eduardo: In einem bestimmten Kontext spielt das vielleicht keine Rolle. Wenn Sie anhand des Beispiels des OP nach dem Namen einer Person in einem multinationalen Kontext suchen, möchten Sie, dass die Suche nicht zu genau ist.
Amir Abiri

(Versehentlich zuvor gesendet) Es gibt jedoch Raum für die Zuordnung von Diakritika zu ihren phonetischen Äquivalenten, um die phonetische Suche zu verbessern. dh ñ => ni liefert bessere Ergebnisse, wenn die zugrunde liegende Suchmaschine die phonetische Suche (z. B. Soundex) unterstützt
Amir Abiri

Ein Anwendungsfall, bei dem das Ändern von año in ano usw. das Entfernen von Nicht-Base64-Zeichen für URLs, IDs usw. entfernt
Ondra Žižka

Antworten:


81

Ich habe dies kürzlich in Java getan:

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

Dies funktioniert wie von Ihnen angegeben:

stripDiacritics("Björn")  = Bjorn

aber es wird zum Beispiel bei Białystok scheitern, weil der łCharakter nicht diakritisch ist.

Wenn Sie einen vollständigen String-Vereinfacher haben möchten, benötigen Sie eine zweite Bereinigungsrunde für einige weitere Sonderzeichen, die keine diakritischen Zeichen sind. Ist diese Karte, habe ich die häufigsten Sonderzeichen, die in unseren Kundennamen erscheinen. Es ist keine vollständige Liste, aber es gibt Ihnen die Idee, wie Sie sie erweitern können. Die immutableMap ist nur eine einfache Klasse aus Google-Sammlungen.

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

Was ist mit Charakteren wie ╨?
Mickthompson

Sie werden jedoch bestanden. Ebenso alle japanischen Schriftzeichen usw.
Andreas Petersson

danke Andreas. Gibt es eine Möglichkeit, diese zu entfernen? Zeichen wie ら が な を 覚 覚 (oder andere) werden in die generierte Zeichenfolge aufgenommen, wodurch die Ausgabe im Wesentlichen unterbrochen wird. Ich versuche, die Ausgabe von simplifiedString als URL-Generator zu verwenden, wie dies StackOverflow für die URLs seiner Fragen tut.
Mickthompson

2
Wie gesagt in der Frage Kommentar. Sie können sich nicht auf die grundlegende Entfernung diakritischer Zeichen verlassen, wenn Sie eine gute Suchmaschine wünschen.
Eduardo

3
Danke Andreas, wirkt wie ein Zauber! (auf 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̌ǵß getestet) :-)
Fortega

24

Das Kernpaket java.text wurde entwickelt, um diesen Anwendungsfall zu behandeln (übereinstimmende Zeichenfolgen, ohne sich um diakritische Zeichen, Groß- und Kleinschreibung usw. zu kümmern).

Konfigurieren Sie a Collator, um PRIMARYUnterschiede in den Zeichen zu sortieren . Erstellen Sie damit CollationKeyfür jede Zeichenfolge eine. Wenn sich Ihr gesamter Code in Java befindet, können Sie den CollationKeydirekt verwenden. Wenn Sie die Schlüssel in einer Datenbank oder einem anderen Index speichern müssen, können Sie sie in ein Byte-Array konvertieren .

Diese Klassen verwenden die Unicode-Standard- Fallfaltungsdaten, um zu bestimmen, welche Zeichen äquivalent sind, und unterstützen verschiedene Zerlegungsstrategien .

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"

Beachten Sie, dass Collatoren länderspezifisch sind. Dies liegt daran, dass die "alphabetische Reihenfolge" von Land zu Land unterschiedlich ist (und sogar im Laufe der Zeit, wie dies bei Spanisch der Fall war). Die CollatorKlasse befreit Sie davon, all diese Regeln nachverfolgen und auf dem neuesten Stand halten zu müssen.


klingt interessant, aber können Sie Ihren Sortierschlüssel in der Datenbank mit select * from person durchsuchen, wo collated_name wie 'bjo%'?
Andreas Petersson

sehr schön, wusste nichts davon. werde das ausprobieren.
Andreas Petersson

Unter Android können die CollationKeys nicht als Präfixe für die Datenbanksuche verwendet werden. Ein Kollatierungsschlüssel der Zeichenfolge awird zu Bytes 41, 1, 5, 1, 5, 0, die Zeichenfolge abwird jedoch zu Bytes 41, 43, 1, 6, 1, 6, 0. Diese Bytesequenzen werden nicht so angezeigt, wie sie sind in vollen Worten (das Byte-Array für den Kollatierungsschlüssel aerscheint nicht im Byte-Array für den Kollatierungsschlüssel für ab)
Grzegorz Adam Hankiewicz

1
@GrzegorzAdamHankiewicz Nach einigen Tests sehe ich, dass die Byte-Arrays verglichen werden können, aber keine Präfixe bilden, wie Sie bemerkt haben. Um eine Präfixabfrage wie bjo%diese durchzuführen, müssten Sie eine Bereichsabfrage durchführen, bei der die Kollatatoren> = bjound <sind bjp(oder was auch immer das nächste Symbol in diesem Gebietsschema sein würde, und es gibt keine programmatische Möglichkeit, dies zu bestimmen).
Erickson


12

Sie können die Normalizer-Klasse verwenden von java.text:

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

Es gibt jedoch noch einige Arbeiten zu erledigen, da Java seltsame Dinge mit nicht konvertierbaren Unicode-Zeichen macht (es ignoriert sie nicht und löst keine Ausnahme aus). Aber ich denke, Sie könnten das als Ausgangspunkt verwenden.


3
Dies funktioniert nicht bei Nicht-ASCII-Diakritikern, wie z. B. auf Russisch. Sie haben auch Diakritika und schlachten außerdem alle asiatischen Saiten. verwende nicht. Verwenden Sie anstelle der Konvertierung in ASCII \\ p {InCombiningDiacriticalMarks} Regexp wie in der Antwort stackoverflow.com/questions/1453171/…
Andreas Petersson


4

Bitte beachten Sie, dass nicht alle dieser Markierungen nur "Markierungen" auf einem "normalen" Zeichen sind, die Sie entfernen können, ohne die Bedeutung zu ändern.

Im Schwedischen sind å ä und ö wahre und richtige erstklassige Zeichen, keine "Variante" eines anderen Zeichens. Sie klingen anders als alle anderen Zeichen, sie sortieren unterschiedlich und sie bewirken, dass Wörter ihre Bedeutung ändern ("mätt" und "matt" sind zwei verschiedene Wörter).


4
Obwohl dies richtig ist, ist dies eher ein Kommentar als eine Antwort auf die Frage.
Simon Forsberg

2

Unicode hat bestimmte diatrische Zeichen (die zusammengesetzte Zeichen sind) und eine Zeichenfolge kann konvertiert werden, sodass das Zeichen und die Diatrik getrennt werden. Dann können Sie einfach die Diatricts aus der Zeichenfolge entfernen und fertig.

Weitere Informationen zu Normalisierung, Zerlegung und Äquivalenz finden Sie unter Der Unicode-Standard auf der Unicode-Homepage .

Wie Sie dies tatsächlich erreichen können, hängt jedoch vom Framework / OS / ... ab, an dem Sie arbeiten. Wenn Sie .NET verwenden, können Sie die String.Normalize- Methode verwenden, die die System.Text.NormalizationForm- Enumeration akzeptiert .


2
Dies ist die Methode, die ich in .NET verwende, obwohl ich einige Zeichen noch manuell zuordnen muss. Sie sind keine Diakritiker, sondern Digraphen. Ähnliches Problem.
Thorarin

1
Konvertiere in die Normalisierungsform "D" (dh zerlegt) und nimm das Basiszeichen.
Richard

2

Der einfachste Weg (für mich) wäre, einfach ein Array mit geringer Zuordnung zu verwalten, das einfach Ihre Unicode-Codepunkte in anzeigbare Zeichenfolgen ändert.

Sowie:

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"
}
: : :

Durch die Verwendung eines spärlichen Arrays können Sie Ersetzungen effizient darstellen, selbst wenn sie sich in weit auseinander liegenden Abschnitten der Unicode-Tabelle befinden. Durch das Ersetzen von Zeichenfolgen können beliebige Sequenzen Ihre diakritischen Zeichen ersetzen (z. B. das æGraphem ae).

Dies ist eine sprachunabhängige Antwort. Wenn Sie also eine bestimmte Sprache im Auge haben, gibt es bessere Möglichkeiten (obwohl sie wahrscheinlich alle ohnehin auf den niedrigsten Ebenen darauf ankommen).


Es ist keine leichte Aufgabe, alle möglichen seltsamen Zeichen hinzuzufügen. Wenn Sie dies nur für wenige Zeichen tun, ist dies eine gute Lösung.
Simon Forsberg

2

Beachten Sie Folgendes: Wenn Sie versuchen, eine einzelne "Übersetzung" jedes Wortes zu erhalten, verpassen Sie möglicherweise einige mögliche Alternativen.

Zum Beispiel könnten auf Deutsch beim Ersetzen des "s-Sets" einige Leute "B" verwenden, während andere "ss" verwenden könnten. Oder ersetzen Sie ein umlautiertes o durch "o" oder "oe". Jede Lösung, die Sie im Idealfall finden, sollte meiner Meinung nach beides beinhalten.


2

In Windows und .NET konvertiere ich nur mithilfe der Zeichenfolgencodierung. Auf diese Weise vermeide ich manuelles Mapping und Codieren.

Versuchen Sie, mit der Zeichenfolgencodierung zu spielen.


3
Können Sie die Zeichenfolgencodierung näher erläutern? Zum Beispiel mit einem Codebeispiel.
Peter Mortensen

2

Im Falle von Deutsch ist es nicht erwünscht, diakritische Zeichen aus Umlauten (ä, ö, ü) zu entfernen. Stattdessen werden sie durch eine Kombination aus zwei Buchstaben (ae, oe, ue) ersetzt. Beispielsweise sollte Björn als Björn (nicht Björn) geschrieben werden, um eine korrekte Aussprache zu erhalten.

Dafür hätte ich lieber eine fest codierte Zuordnung, bei der Sie die Ersetzungsregel für jede Sonderzeichengruppe einzeln definieren können.


0

Zum späteren Nachschlagen finden Sie hier eine C # -Erweiterungsmethode, mit der Akzente entfernt werden.

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");
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.