Удалить диакритические знаки (ǹ ń ň ń ņ ṅ ṇ ṋ ṉ ɲ ƞ ᶇ ɳ ȵ) из Unicode-символов

Я смотрю на алгоритм, который может отображать между символами с диакритикой (Тильда, circumflex, курсор, умляут, Кэрон) и их "простой" характер.

например:

ń  ǹ  ň  ñ  ṅ  ņ  ṇ  ṋ  ṉ  ̈  ɲ  ƞ ᶇ ɳ ȵ  --> n
á --> a
ä --> a
ấ --> a
ṏ --> o

Etc.

  1. Я хочу сделать это на Java, хотя я подозреваю, что это должно быть что-то Unicode-y и должно быть выполнимо достаточно легко в любом язык.

  2. цель: чтобы легко искать слова с диакритическими метками. Например, если у меня есть база данных теннисистов, и Björn_Borg введен, я также сохраню Bjorn_Borg, чтобы я мог найти его, если кто-то входит в Bjorn, а не Björn.

12 ответов


недавно я сделал это на 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;
}

это будет делать, как вы указали:

stripDiacritics("Björn")  = Bjorn

но он потерпит неудачу, например, Белосток, потому что ł характер не является диакритическим.

если вы хотите иметь полноценную строковых функциях упрощения выражений, вам понадобится второй этап очистки, для некоторых более специальных символов, которые не являются диакритические знаки. Является ли эта карта, я включил наиболее распространенные специальные символы, которые появляются в наших именах клиентов. Это не полный список, но он даст вам представление о том, как его расширить. ImmutableMap - это простой класс из google-коллекций.

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

ядро java.текстовый пакет был разработан для решения этого варианта использования (соответствующие строки, не заботясь о диакритике, случае и т. д.).

настройка Collator сортировка PRIMARY различия в символах. С этим создайте CollationKey для каждой строки. Если весь код в Java, вы можете использовать CollationKey напрямую. Если вам нужно сохранить ключи в базе данных или другом индексе, вы можете преобразовать его в байт массив.

эти классы использовать Unicode standard случай складывания данных, чтобы определить, какие символы эквивалентны, и поддержка различных распад стратегии.

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"

обратите внимание, что параметры сортировки зависят от локали. Это связано с тем, что" алфавитный порядок " отличается между локалями (и даже со временем, как это было в случае с испанским языком). The Collator класс избавляет вас от необходимости отслеживать все эти правила и хранить их до дата.


часть Apache Commons Lang как Вер. 3.1.

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

возвращает An


можно использовать нормализатор класс С java.text:

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

но есть еще некоторая работа, так как Java делает странные вещи с непереводимыми символами Юникода (он не игнорирует их, и он не бросает исключение). Но я думаю, вы могли бы использовать это как отправную точку.


есть проект доклада о складывании символов на веб-сайте unicode, который имеет много соответствующего материала. Смотри раздел 4.1. "Алгоритм складывания".

здесь Обсуждение и реализация удаления диакритического маркера с помощью Perl.

эти существующие вопросы SO связаны:


обратите внимание, что не все эти метки являются просто "метками" на каком-то "нормальном" символе, которые вы можете удалить без изменения значения.

в шведском языке å ä и ö-истинные и правильные первоклассные символы, а не какой-то "вариант" какого-то другого символа. Они звучат иначе, чем все остальные персонажи, они сортируются по-другому, и они заставляют слова менять значение ("mätt" и "matt" - это два разных слова).


Юникод имеет специфические diatric символы (составные символы) и строку можно преобразовать так, чтобы характер и diatrics разделяются. Затем вы можете просто удалить диатрики из строки, и вы в основном сделали.

для получения дополнительной информации о нормализации, разложениях и эквивалентности см. стандарт Unicode в Домашняя страница Unicode.

однако, как вы можете достичь этого, зависит от framework / OS/... ты над этим работаешь. Если вы используете .NET, вы можете использовать строку.Нормализовать способ принятия


самый простой способ (для меня) - просто поддерживать разреженный массив отображения, который просто изменяет ваши кодовые точки Unicode в отображаемые строки.

, например:

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

использование редкие array позволит вам эффективно представлять замены, даже если они находятся в широко разнесенных разделах таблицы Unicode. Замена строк позволит произвольным последовательностям заменить ваши диакритики (например,æ графема становится ae).

Это язык-агностический ответ, поэтому, если у вас есть конкретный язык в виду, будут лучшие способы (хотя все они, вероятно, дойдут до этого на самых низких уровнях в любом случае).


Что-то рассмотреть: если вы идете по пути, пытаясь получить один "перевод" каждого слова, вы можете пропустить некоторые возможные альтернативы.

например, на немецком языке при замене " s-set "некоторые люди могут использовать" B", в то время как другие могут использовать"ss". Или, заменяя umlauted o на "o "или"oe". Любое решение, которое вы придумаете, в идеале, я думаю, должно включать оба.


в Windows и .NET я просто конвертирую, используя кодировку строк. Таким образом, я избегаю ручного отображения и кодирования.

попробуйте играть со Строковой кодировкой.


в случае немецкого языка не требуется удалять диакритики из Umlauts (ä, ö, ü). Вместо этого они заменяются двухбуквенной комбинацией (ae ,oe, ue) Например, Björn следует писать как Bjoern (не Bjorn), чтобы иметь правильное произношение.

для этого я бы предпочел жестко закодированное отображение, где вы можете определить правило замены индивидуально для каждой специальной группы символов.


для дальнейшего использования, Вот метод расширения C#, который удаляет акценты.

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