Удалить диакритические знаки ((ǹ ň ñ ṅ ņ ṇ ṋ ṉ ̈ ɲ ƞ ᶇ ȵ ȵ) из символов Unicode - PullRequest
84 голосов
/ 21 сентября 2009

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

Например:

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

Etc.

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

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

Ответы [ 12 ]

74 голосов
/ 21 сентября 2009

Я недавно сделал это на 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;
    }
}
23 голосов
/ 21 сентября 2009

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

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

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

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 избавляет вас от необходимости отслеживать все эти правила и обновлять их.

15 голосов
/ 14 октября 2012

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

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

возвращает An

11 голосов
/ 21 сентября 2009

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

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

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

10 голосов
/ 21 сентября 2009

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

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

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

4 голосов
/ 01 марта 2010

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

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

2 голосов
/ 08 февраля 2013

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

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

2 голосов
/ 21 сентября 2009

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

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

2 голосов
/ 21 сентября 2009

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

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

2 голосов
/ 21 сентября 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"
}
: : :

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

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

...