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

Я ищу алгоритм, который может отображать символы с диакритическими знаками (тильда, с циркумфлексом, каретка, умлаут, caron) и их" простой "характер.

Например:

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

И Т. Д.

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

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


person flybywire    schedule 21.09.2009    source источник
comment
Это зависит от того, в какой среде вы программируете, хотя вам, вероятно, придется поддерживать какую-то таблицу сопоставления вручную. Итак, какой язык вы используете?   -  person Thorarin    schedule 21.09.2009
comment
Обратите внимание, что некоторые буквы вроде ñ en.wikipedia.org/wiki/%C3%91 не следует удалять диакритические знаки в целях поиска. Google правильно различает испанское ano (анус) и año (год). Поэтому, если вам действительно нужна хорошая поисковая система, вы не можете полагаться на простое удаление диакритических знаков.   -  person Eduardo    schedule 08.12.2010
comment
@ Эдуардо: В данном контексте это может не иметь значения. Используя пример, приведенный OP, при поиске имени человека в многонациональном контексте вы действительно хотите, чтобы поиск не был слишком точным.   -  person Amir Abiri    schedule 17.07.2012
comment
(Случайно отправлено предыдущим) Тем не менее, есть место для сопоставления диакритических знаков с их фонетическими эквивалентами, чтобы улучшить фонетический поиск. т.е. ñ = ›ni будет давать лучшие результаты, если основная поисковая система поддерживает поиск на основе фонетики (например, soundex).   -  person Amir Abiri    schedule 17.07.2012
comment
Пример использования, когда при изменении año на ano и т. Д. Удаляются символы, отличные от base64, для URL-адресов, идентификаторов и т. Д.   -  person Ondra Žižka    schedule 03.11.2012
comment
StringUtils из библиотеки apache.commons имеет метод stripAccents, и он работает очень хорошо. commons.apache.org/proper/ общий-lang / apidocs / org / apache /   -  person Guilherme Guini    schedule 21.02.2019


Ответы (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;
    }
}
person Andreas Petersson    schedule 21.09.2009
comment
а как насчет таких персонажей, как ╨? - person mickthompson; 09.03.2010
comment
они пройдут - правда. аналогично всем японским иероглифам и т. д. - person Andreas Petersson; 10.03.2010
comment
спасибо Андреас. Есть ли способ их удалить? Такие символы, как ら が な を 覚 男 (или другие), будут включены в сгенерированную строку, и это в основном нарушит вывод. Я пытаюсь использовать вывод simpleifiedString в качестве генератора URL-адресов, как это делает StackOverflow для URL-адресов своих вопросов. - person mickthompson; 10.03.2010
comment
Как я уже сказал в комментарии к вопросу. Вы не можете полагаться на простое удаление диакритических знаков, если вам нужна хорошая поисковая система. - person Eduardo; 08.12.2010
comment
Спасибо Андреас, работает как шарм! (проверено на r̀r̂r̃r̈rʼŕřt̀t̂ẗţỳỹẙyʼy̎yÿŷp̂p̈s̀s̀s̈s̊sʼs̸śŝŞşšd̂d̃d̈ďdʼḑf̈f̸g̀g̃g̈gʼģq́ĝǧḧĥj̈jʼḱk̂k̈k̸ǩl̂l̃l̈Łłĉc̃c̈c̊vcnnmnmnmnmnn)n) - person Fortega; 01.04.2011
comment
Отлично, спасибо, действительно полезно, но для меня сработало только так (\\ p {InCombiningDiacriticalMarks} +); . Сохранение других скоб приведет к сбою !! Но для меня сделка заключена, еще раз спасибо. - person Alexandre; 04.07.2014
comment
Обратите внимание, что ни одна из форм нормализации Unicode (NFC, NFKC, NFD, NFKD) не поможет транслитерировать Bjørn, поскольку СТРОЧНАЯ ЛАТИНСКАЯ БУКВА O СО СТРОЖКОЙ (U + 00F8) не считается комбинацией. Для этого вам, вероятно, понадобится настоящий транслитератор, например ICU. - person 200_success; 31.08.2017

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

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

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

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"

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

person erickson    schedule 21.09.2009
comment
звучит интересно, но можете ли вы найти свой ключ сопоставления в базе данных с помощью select * from person, где collated_name как 'bjo%' ?? - person Andreas Petersson; 21.09.2009
comment
очень красиво, не знал об этом. попробую это. - person Andreas Petersson; 22.09.2009
comment
На Android CollationKeys нельзя использовать в качестве префиксов для поиска в базе данных. Ключ сопоставления строки a превращается в байты 41, 1, 5, 1, 5, 0, а строка ab превращается в байты 41, 43, 1, 6, 1, 6, 0. Эти последовательности байтов не отображаются как есть в полных словах (байтовый массив для ключа сопоставления a не появляется в байтовом массиве для ключа сопоставления для ab) - person Grzegorz Adam Hankiewicz; 07.04.2019
comment
@GrzegorzAdamHankiewicz После некоторого тестирования я вижу, что байтовые массивы можно сравнивать, но не формируют префиксы, как вы заметили. Итак, чтобы выполнить префиксный запрос, такой как bjo%, вам нужно будет выполнить запрос диапазона, где сопоставителями являются ›= bjo и‹ ​​bjp (или любой другой символ, который будет в этой локали, и нет программного способа определить это) . - person erickson; 08.04.2019

Это часть Apache Commons Lang от вер. 3.1.

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

возвращает An

person Kenston Choi    schedule 14.10.2012
comment
Для Ø снова получается Ø - person Mike Argyriou; 10.07.2014
comment
Спасибо, Майк, что указал на это. Метод обрабатывает только акценты. Результат ń ǹ ň ñ ņ ṇ ṉ ̈ ɲ ƞ ᶇ ɳ ȵ равен n n n n n n n n n ɲ ƞ ᶇ ɳ ȵ - person Kenston Choi; 28.08.2014

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

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

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

person nils    schedule 21.09.2009
comment
это не будет работать с диакритическими знаками, отличными от ascii, например, в русском языке, у них тоже есть диакритические знаки, и, кроме того, убираются все азиатские строки. не используй. вместо преобразования в ascii используйте регулярное выражение \\ p {InCombiningDiacriticalMarks}, как в answer stackoverflow.com/questions/1453171/ - person Andreas Petersson; 21.09.2009

На веб-сайте Unicode есть черновик отчета о сворачивании символов, в котором много соответствующего материала. См., В частности, раздел 4.1. «Алгоритм складывания».

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

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

person ire_and_curses    schedule 21.09.2009

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

В шведском языке это настоящие и правильные первоклассные символы, а не какой-то «вариант» какого-то другого персонажа. Они звучат иначе, чем все другие символы, они сортируются по-другому и заставляют слова менять значение («mtt» и «matt» - это два разных слова).

person unwind    schedule 01.03.2010
comment
Хотя это правильно, это скорее комментарий, чем ответ на вопрос. - person Simon Forsberg; 06.04.2013

Unicode имеет определенные диатрические символы (которые являются составными символами), и строка может быть преобразована так, чтобы символ и диатрика были разделены. Затем вы можете просто удалить диатрику из строки, и в основном все готово.

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

Однако то, как вы на самом деле можете этого добиться, зависит от платформы / OS / ..., над которой вы работаете. Если вы используете .NET, вы можете использовать String .Normalize, принимающий System.Text.NormalizationForm перечисление.

person Lucero    schedule 21.09.2009
comment
Это метод, который я использую в .NET, хотя мне все равно приходится вручную отображать некоторые символы. Это не диакритические знаки, а орграфы. Хотя похожая проблема. - person Thorarin; 21.09.2009
comment
Преобразуйте в форму нормализации D (т.е. разложенную) и возьмите базовый символ. - person Richard; 21.09.2009

Самый простой способ (для меня) - просто поддерживать разреженный массив сопоставления, который просто заменяет ваши кодовые точки 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"
}
: : :

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

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

person paxdiablo    schedule 21.09.2009
comment
Добавить туда всех возможных странных персонажей - непростая задача. Когда это делается только для нескольких персонажей, это хорошее решение. - person Simon Forsberg; 06.04.2013

На что следует обратить внимание: если вы пойдете по пути попыток получить единственный «перевод» каждого слова, вы можете упустить некоторые возможные варианты.

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

person Beska    schedule 21.09.2009

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

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

person Viktor Jevdokimov    schedule 21.09.2009
comment
Можете ли вы подробнее рассказать о кодировке строк? Например, с примером кода. - person Peter Mortensen; 25.11.2012

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

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

person jalbert    schedule 08.02.2013

Для справки в будущем, вот метод расширения 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");
}
person Nathan Baulch    schedule 26.09.2009