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

Я рассматриваю алгоритм, который может отображать символы с диакритикой (tilde, circumflex, caret, umlaut, caron) и их "простой" символ.

Например:

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

Etc.

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

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

Ответ 1

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

но это, к примеру, Bialystok, потому что символ ł не диакритический.

Если вы хотите иметь полномасштабный упроститель строк, вам понадобится второй раунд очистки, для некоторых более специальных символов, которые не являются диакритическими. Является ли эта карта, я включил наиболее распространенные специальные символы, которые появляются в именах наших клиентов. Это не полный список, но он даст вам представление о том, как это сделать. 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;
    }
}

Ответ 2

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

Настройте 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"

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

Ответ 3

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

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

возвращает An

Ответ 4

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

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

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

Ответ 6

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

Шведский, & aring; & AUML; и & omу; являются истинными и правильными первоклассными персонажами, а не каким-то "вариантом" какого-либо другого персонажа. Они отличаются от всех других персонажей, они различаются, и они меняют смысл слова ( "m & auml; tt" и "matt" - это два разных слова).

Ответ 7

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

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

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

Ответ 8

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

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

Ответ 9

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

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

Ответ 10

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

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

Ответ 11

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

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

Ответ 12

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

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