Есть ли способ избавиться от ударений и преобразовать целую строку в обычные буквы?

263

Есть ли лучший способ избавиться от акцентов и сделать эти буквы регулярными, кроме использования String.replaceAll()метода и замены букв одна за другой? Пример:

Входные данные: orčpžsíáýd

Вывод: orcpzsiayd

Не нужно включать все буквы с акцентами, как русский алфавит или китайский.

Мартин
источник

Ответы:

387

Используйте, java.text.Normalizerчтобы справиться с этим для вас.

string = Normalizer.normalize(string, Normalizer.Form.NFD);
// or Normalizer.Form.NFKD for a more "compatable" deconstruction 

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

string = string.replaceAll("[^\\p{ASCII}]", "");

Если ваш текст в Unicode, вы должны использовать это вместо:

string = string.replaceAll("\\p{M}", "");

Для юникода \\P{M}совпадает с базовым глифом и \\p{M}(нижний регистр) соответствует каждому акценту.

Спасибо GarretWilson за указатель и регулярно-expressions.info за большое руководство по юникоду .

Эрик Робертсон
источник
7
Это скомпилирует регулярное выражение каждый раз, что хорошо, если вам нужно только один раз, но если вам нужно сделать это с большим количеством текста, предварительная компиляция регулярного выражения является победой.
Дэвид Конрад
3
Обратите внимание, что не все латинские буквы разлагаются на ASCII + ударения. Это убьет, например. "Латинская {заглавная, маленькая} буква l с ударением" используется в польском языке.
Михал Политовски
12
Это хороший подход, но удаление всех символов, не относящихся к ASCII, является излишним и, вероятно, удалит то, что вам не нужно, как указали другие. Было бы лучше удалить все Unicode-метки; включая непространственные метки, межстрочные / комбинированные метки и ограждающие метки. Вы можете сделать это с string.replaceAll("\\p{M}", ""). Смотрите регулярно-expressions.info/unicode.html для получения дополнительной информации.
Гаррет Уилсон
4
Вы, вероятно, захотите использовать Normalizer.Form.NFKD вместо NFD - NFKD преобразует такие вещи, как лигатуры, в символы ascii (например, от f до fi), NFD не будет этого делать.
chesterm8
2
@ chesterm8, интересно, что NFKD конвертирует «" »в« fi », но не конвертирует« Æ »в« AE ». Я думаю, мне придется вызвать данные Unicode, чтобы выяснить, почему, но это было не то, что я ожидал.
Гаррет Уилсон
136

Начиная с 2011 года вы можете использовать Apache Commons StringUtils.stripAccents (входная информация) (начиная с версии 3.0):

    String input = StringUtils.stripAccents("Tĥïŝ ĩš â fůňķŷ Šťŕĭńġ");
    System.out.println(input);
    // Prints "This is a funky String"

Примечание:

Принятый ответ (Эрика Робертсона) не работает для Ø или Ł. Apache Commons 3.5 также не работает для Ø, но он работает для Ł. Прочитав статью в Википедии для Ø , я не уверен, что ее следует заменить на «O»: это отдельное письмо на норвежском и датском языках, расположенное в алфавитном порядке после «z». Это хороший пример ограничений подхода «раздевания».

Давидс
источник
2
Я вижу, что есть открытый отчет об ошибке для Ł , @KarolS. Кто-то отправил запрос на удаление, но он не прошел некоторые тесты и не обновлялся с июля прошлого года.
DavidS
1
5 дней назад было обновление, и запрос на объединение был объединен.
EpicPandaForce
6
Commons Lang 3.5 был выпущен несколько дней назад. Я подтвердил, что это работает на Ł сейчас. Это не работает на Ø. Читая вики-статью для Ø , я не уверен, что ее следует заменить на «O»: это отдельная буква на норвежском и датском языках в алфавитном порядке после «z». Это хороший пример ограничений подхода «раздевания».
DavidS
2
Если вы не хотите включать библиотеку, вы можете легко взять два метода, задействованных в этой функции, из источника по адресу commons.apache.org/proper/commons-lang/apidocs/src-html/org/…
lujop
2
Как датчанин, датский / норвежский ø так же, как французский œ и немецкий / шведский / венгерский / эстонский и т. Д. Origin происходит как короткий способ написать oe. Таким образом, в зависимости от вашей цели это может быть замена, которую вы хотите.
Оле В.В.
57

Решение от @ virgo47 очень быстрое, но приблизительное. Принятый ответ использует нормализатор и регулярное выражение. Мне было интересно, какую часть времени занимал Normalizer по сравнению с регулярным выражением, поскольку удаление всех символов, не относящихся к ASCII, может быть выполнено без регулярного выражения:

import java.text.Normalizer;

public class Strip {
    public static String flattenToAscii(String string) {
        StringBuilder sb = new StringBuilder(string.length());
        string = Normalizer.normalize(string, Normalizer.Form.NFD);
        for (char c : string.toCharArray()) {
            if (c <= '\u007F') sb.append(c);
        }
        return sb.toString();
    }
}

Небольшие дополнительные ускорения можно получить, записав в char [] и не вызывая toCharArray (), хотя я не уверен, что уменьшение ясности кода заслуживает этого:

public static String flattenToAscii(String string) {
    char[] out = new char[string.length()];
    string = Normalizer.normalize(string, Normalizer.Form.NFD);
    int j = 0;
    for (int i = 0, n = string.length(); i < n; ++i) {
        char c = string.charAt(i);
        if (c <= '\u007F') out[j++] = c;
    }
    return new String(out);
}

Преимущество этого варианта состоит в правильности использования нормализатора и скорости использования таблицы. На моей машине этот примерно в 4 раза быстрее, чем принятый ответ, и в 6,6-7 раз медленнее, чем у @ virgo47 (принятый ответ примерно в 26 раз медленнее, чем у @ virgo47 на моей машине).

Дэвид Конрад
источник
2
outдолжен быть изменен, чтобы соответствовать количеству допустимых символов, jпрежде чем он будет использован для создания строкового объекта.
Лефтерис Э
4
У меня есть возражение против этого решения. Представьте себе, что ввод "åøåá". Current flattenToAsciiсоздает результат "aa ..", где точки представляют \ u0000. Это не хорошо. Первый вопрос - как изобразить «ненормализуемых» персонажей? Допустим, это будет?, Или мы можем оставить там NULL char, но в любом случае мы должны сохранить их правильное положение (как это делает решение регулярных выражений). Для этого условие if в цикле должно быть примерно таким: if (c <= '\u007F') out[j++] = c; else if (Character.isLetter(c)) out[j++] = '?';оно немного замедлит его, но в первую очередь должно быть правильным. ;-)
virgo47
Объявление моего последнего комментария (слишком плохо, что они не могут быть длиннее) - возможно, позитивный дубль ( isLetter) не правильный, но я не нашел лучшего. Я не эксперт по Unicode, поэтому я не знаю, как лучше определить класс одиночного символа, который заменяет оригинальный символ. Письма работают нормально для большинства приложений / использования.
virgo47
1
Вы, вероятно, захотите использовать Normalizer.Form.NFKD вместо NFD - NFKD преобразует такие вещи, как лигатуры, в символы ascii (например, от f до fi), NFD не будет этого делать.
chesterm8
2
Для нас мы хотели полностью убрать персонажа. Чтобы не было завершающих нулевых символов, я удалил их с помощью альтернативного конструктора String: return new String (out, 0, j);
Майк Самарас
30

РЕДАКТИРОВАТЬ: Если вы не застряли с Java <6, и скорость не критична и / или таблица перевода слишком ограничена, используйте ответ Дэвида. Смысл в том, чтобы использовать Normalizer(представленный в Java 6) вместо таблицы перевода внутри цикла.

Хотя это не «идеальное» решение, оно хорошо работает, когда вы знаете диапазон (в нашем случае Latin1,2), работало до Java 6 (хотя это и не является реальной проблемой) и намного быстрее, чем наиболее рекомендуемая версия (может или может не будет проблемой)

    /**
 * Mirror of the unicode table from 00c0 to 017f without diacritics.
 */
private static final String tab00c0 = "AAAAAAACEEEEIIII" +
    "DNOOOOO\u00d7\u00d8UUUUYI\u00df" +
    "aaaaaaaceeeeiiii" +
    "\u00f0nooooo\u00f7\u00f8uuuuy\u00fey" +
    "AaAaAaCcCcCcCcDd" +
    "DdEeEeEeEeEeGgGg" +
    "GgGgHhHhIiIiIiIi" +
    "IiJjJjKkkLlLlLlL" +
    "lLlNnNnNnnNnOoOo" +
    "OoOoRrRrRrSsSsSs" +
    "SsTtTtTtUuUuUuUu" +
    "UuUuWwYyYZzZzZzF";

/**
 * Returns string without diacritics - 7 bit approximation.
 *
 * @param source string to convert
 * @return corresponding string without diacritics
 */
public static String removeDiacritic(String source) {
    char[] vysl = new char[source.length()];
    char one;
    for (int i = 0; i < source.length(); i++) {
        one = source.charAt(i);
        if (one >= '\u00c0' && one <= '\u017f') {
            one = tab00c0.charAt((int) one - '\u00c0');
        }
        vysl[i] = one;
    }
    return new String(vysl);
}

Тесты на моем HW с 32-битным JDK показывают, что он выполняет преобразование из файла aeelstc89FDC из aeelstc89FDC 1 миллион раз за ~ 100 мс, в то время как метод нормализатора делает это за 3,7 с (в 37 раз медленнее). Если ваши потребности в производительности и вы знаете диапазон ввода, это может быть для вас.

Наслаждаться :-)

virgo47
источник
1
Большая часть медлительности предлагаемой версии связана с регулярным выражением, а не с нормализатором. Использование нормализатора, но удаление не-ASCII символов «вручную» происходит быстрее, хотя и не так быстро, как ваша версия. Но это работает для всего Unicode вместо только latin1 и latin2.
Дэвид Конрад
Я расширил это, чтобы работать с большим количеством символов, pastebin.com/FAAm6a2j. Обратите внимание, что он не будет корректно работать с такими символами, как DŽ (DZ). Он будет производить только 1 символ из него. Также моя функция использует char вместо строк, что быстрее, если вы в любом случае обрабатываете char, поэтому вам не нужно конвертировать.
Джеймс Т
Эй, я не понимаю, что означают эти буквы в поле tab00c0? например, "AAAAAAACEEEEIIII" или "lLlNnNnNnnNnOoOo" и т. д. Никогда не видел их раньше. Где вы их нашли? Кроме того, почему бы вам просто не использовать соответствующие коды?
ThanosFisherman
@ThanosF просто попробуйте пройти код (с отладчиком, если необходимо). Это делается для каждого символа в строке: «Этот символ находится между \ u00c0 и \ u017f? Если так, замените его на 7-битный ASCII-символ из таблицы». Таблица только охватывает две страницы кодирования (латиница 1 и 2) с их 7-битными эквивалентами. Так что, если это символ с кодом \ u00e0 (а), он займет свое 7-битное приближение из 32-й позиции таблицы (e0-c0 = 32) - это «а». Некоторые символы не являются буквами, они оставлены там со своим кодом.
virgo47
Спасибо за ваше объяснение. Где я могу найти эти страницы кодирования, чтобы я мог расширить эту переменную на свой язык? (Греческий) Принятый ответ уже выполняет работу по замене греческих букв с акцентом, но я тоже хотел попробовать ваш метод и выполнить некоторые тесты :)
ThanosFisherman
22
System.out.println(Normalizer.normalize("àèé", Normalizer.Form.NFD).replaceAll("\\p{InCombiningDiacriticalMarks}+", ""));

работал на меня. Вывод приведенного выше фрагмента дает «aee», что я и хотел, но

System.out.println(Normalizer.normalize("àèé", Normalizer.Form.NFD).replaceAll("[^\\p{ASCII}]", ""));

не сделал никакой замены.

Нико
источник
1
В подтверждение этого ... обычно ASCII работает просто отлично, но я столкнулся с этой проблемой в Linux (64b) с JRockit (1.6.0_29 64b). Не могу подтвердить это с помощью какой-либо другой установки, не могу подтвердить это соответствие, но я могу подтвердить, что другое предлагаемое решение сработало, и за это я голосую. :-) (Кстати: он сделал некоторую замену, но не достаточно, он изменил Ú, например, на U, но не на a.)
virgo47
1
Вы, вероятно, захотите использовать Normalizer.Form.NFKD вместо NFD - NFKD преобразует такие вещи, как лигатуры, в символы ascii (например, от f до fi), NFD не будет этого делать.
chesterm8
@KarolS я не вижу любого из них , содержащих каких - либо акценты
EIS
@eis Косая черта в букве считается диакритическим: en.wikipedia.org/wiki/Diacritic И если вы выберете более строгое определение «акцента», как на этой странице Википедии, то диарез не является акцентом, поэтому ответ Нико все еще не так.
Кароль С,
6

В зависимости от языка, они могут рассматриваться не как акценты (которые изменяют звучание буквы), а как диакритические знаки

https://en.wikipedia.org/wiki/Diacritic#Languages_with_letters_containing_diacritics

«Боснийский и хорватский имеют символы č, ć, đ, š и ž, которые считаются отдельными буквами и перечислены как таковые в словарях и других контекстах, в которых слова перечислены в алфавитном порядке».

Удаление их может по своей сути изменить значение слова или заменить буквы на совершенно другие.

NinjaCat
источник
5
Согласовано. Например, на шведском языке: "höra" (слышать) -> "hora" (шлюха)
Christoffer Hammarström
14
Неважно, что они имеют в виду. Вопрос в том, как их удалить.
Эрик Робертсон
7
Эрик: важно, как они называются. Если вопрос спрашивает, как убрать акценты, и если они не являются акцентами, то ответ может быть не просто как удалить все те вещи, которые выглядят как акценты. Хотя, вероятно, это должен быть комментарий, а не ответ.
Smig
4
Я думаю, что нормальным вариантом использования этого является поиск, особенно поиск смешанных языков, часто с вводом английской клавиатуры, и в этом случае лучше получать ложные срабатывания, чем ложные отрицания.
nilskp
3

Я столкнулся с той же проблемой, связанной с проверкой равенства строк. Одна из сравниваемых строк имеет код символа ASCII 128-255 .

т.е. неразрывный пробел - [Hex - A0] пробел [Hex - 20]. Показать неразрывный пробел над HTML. Я использовал следующее spacing entities. Их характер и его байты похожи&emsp is very wide space[ ]{-30, -128, -125}, &ensp is somewhat wide space[ ]{-30, -128, -126}, &thinsp is narrow space[ ]{32} , Non HTML Space {}

String s1 = "My Sample Space Data", s2 = "My Sample Space Data";
System.out.format("S1: %s\n", java.util.Arrays.toString(s1.getBytes()));
System.out.format("S2: %s\n", java.util.Arrays.toString(s2.getBytes()));

Вывод в байтах:

S1: [77, 121, 3283, 97, 109, 112, 108, 101, 3283, 112, 97, 99, 101 32, 68, 97, 116, 97] S2: [77, 121 -30, -128, -125, 83, 97, 109, 112, 108, 101,, -30, -128, -12583, 112, 97, 99, 101,, -30, -128, -12568, 97, 116, 97]

Используйте приведенный ниже код для различных пространств и их байт-кодов: wiki for List_of_Unicode_characters

String spacing_entities = "very wide space,narrow space,regular space,invisible separator";
System.out.println("Space String :"+ spacing_entities);
byte[] byteArray = 
    // spacing_entities.getBytes( Charset.forName("UTF-8") );
    // Charset.forName("UTF-8").encode( s2 ).array();
    {-30, -128, -125, 44, -30, -128, -126, 44, 32, 44, -62, -96};
System.out.println("Bytes:"+ Arrays.toString( byteArray ) );
try {
    System.out.format("Bytes to String[%S] \n ", new String(byteArray, "UTF-8"));
} catch (UnsupportedEncodingException e) {
    e.printStackTrace();
}
  • SC ASCII транслитерация строки Unicode для Java. unidecode

    String initials = Unidecode.decode( s2 );
  • ➩ используя Guava: Google Core Libraries for Java.

    String replaceFrom = CharMatcher.WHITESPACE.replaceFrom( s2, " " );

    Для кодирования URL для пробела используйте библиотеку Guava.

    String encodedString = UrlEscapers.urlFragmentEscaper().escape(inputString);
  • ➩ Чтобы преодолеть эту проблему, используется String.replaceAll()с некоторыми RegularExpression.

    // \p{Z} or \p{Separator}: any kind of whitespace or invisible separator.
    s2 = s2.replaceAll("\\p{Zs}", " ");
    
    
    s2 = s2.replaceAll("[^\\p{ASCII}]", " ");
    s2 = s2.replaceAll(" ", " ");
  • ➩ Использование java.text.Normalizer.Form . Это перечисление предоставляет константы четырех форм нормализации Unicode, которые описаны в Стандартном приложении № 15 к Unicode - Формы нормализации Unicode и два метода для доступа к ним.

    введите описание изображения здесь

    s2 = Normalizer.normalize(s2, Normalizer.Form.NFKC);

Тестирование строки и выводов по различным подходам, таким как ➩ Unidecode, Normalizer, StringUtils .

String strUni = "Tĥïŝ ĩš â fůňķŷ Šťŕĭńġ Æ,Ø,Ð,ß";

// This is a funky String AE,O,D,ss
String initials = Unidecode.decode( strUni );

// Following Produce this o/p: Tĥïŝ ĩš â fůňķŷ Šťŕĭńġ Æ,Ø,Ð,ß
String temp = Normalizer.normalize(strUni, Normalizer.Form.NFD);
Pattern pattern = Pattern.compile("\\p{InCombiningDiacriticalMarks}+");
temp = pattern.matcher(temp).replaceAll("");

String input = org.apache.commons.lang3.StringUtils.stripAccents( strUni );

Использование Unidecode - это best choiceмой окончательный код, показанный ниже.

public static void main(String[] args) {
    String s1 = "My Sample Space Data", s2 = "My Sample Space Data";
    String initials = Unidecode.decode( s2 );
    if( s1.equals(s2)) { //[ , ] %A0 - %2C - %20 « http://www.ascii-code.com/
        System.out.println("Equal Unicode Strings");
    } else if( s1.equals( initials ) ) {
        System.out.println("Equal Non Unicode Strings");
    } else {
        System.out.println("Not Equal");
    }

}
Яши
источник
3

Я предлагаю Junidecode . Он будет обрабатывать не только «Ł» и «Ø», но также хорошо работает для транскрибирования с других алфавитов, таких как китайский, в латинский алфавит.

OlgaMaciaszek
источник
1
Выглядит многообещающе, но я бы хотел, чтобы это был более активный / поддерживаемый проект, доступный на Maven.
Фил
2

Решение @David Conrad - самое быстрое, что я пробовал использовать нормализатор, но в нем есть ошибка. Он в основном удаляет символы, которые не являются ударением, например, китайские и другие буквы, такие как æ, все удаляются. Символы, которые мы хотим вырезать, не являются пробелами, символами, которые не занимают дополнительной ширины в последней строке. Эти символы нулевой ширины в основном объединяются в каком-то другом символе. Если вы видите их изолированными как символы, например, вот так `, я предполагаю, что они объединены с символом пробела.

public static String flattenToAscii(String string) {
    char[] out = new char[string.length()];
    String norm = Normalizer.normalize(string, Normalizer.Form.NFD);

    int j = 0;
    for (int i = 0, n = norm.length(); i < n; ++i) {
        char c = norm.charAt(i);
        int type = Character.getType(c);

        //Log.d(TAG,""+c);
        //by Ricardo, modified the character check for accents, ref: http://stackoverflow.com/a/5697575/689223
        if (type != Character.NON_SPACING_MARK){
            out[j] = c;
            j++;
        }
    }
    //Log.d(TAG,"normalized string:"+norm+"/"+new String(out));
    return new String(out);
}
Рикардо Фрейтас
источник
1

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

    public String flattenToAscii(String s) {
                if(s == null || s.trim().length() == 0)
                        return "";
                return Normalizer.normalize(s, Normalizer.Form.NFD).replaceAll("[\u0300-\u036F]", "");
}

Это более эффективно, чем replaceAll ("[^ \ p {ASCII}]", "")), и если вам не нужны диакритические знаки (как в вашем примере).

В противном случае вы должны использовать шаблон p {ASCII}.

С уважением.

Жар
источник
0

Я думаю, что лучшее решение - конвертировать каждый символ в HEX и заменить его другим HEX. Это потому, что есть 2 Unicode, набрав:

Composite Unicode
Precomposed Unicode

Например, «Ồ», написанный Composite Unicode, отличается от «Ồ», написанного Precomposed Unicode. Вы можете скопировать мои образцы символов и преобразовать их, чтобы увидеть разницу.

In Composite Unicode, "Ồ" is combined from 2 char: Ô (U+00d4) and ̀ (U+0300)
In Precomposed Unicode, "Ồ" is single char (U+1ED2)

Я разработал эту функцию для некоторых банков, чтобы преобразовать информацию перед отправкой в ​​основной банк (обычно не поддерживающий Юникод), и столкнулся с этой проблемой, когда конечные пользователи используют множественный ввод Юникода для ввода данных. Поэтому я думаю, что переход на HEX и его замена - самый надежный способ.

Хоанг Тран
источник
-1

Если кто-то пытается сделать это в kotlin, этот код работает как шарм. Чтобы избежать несоответствий, я также использую .toUpperCase и Trim (). тогда я разыграю эту функцию:

   fun stripAccents(s: String):String{

   if (s == null) {
      return "";
   }

val chars: CharArray = s.toCharArray()

var sb = StringBuilder(s)
var cont: Int = 0

while (chars.size > cont) {
    var c: kotlin.Char
    c = chars[cont]
    var c2:String = c.toString()
   //these are my needs, in case you need to convert other accents just Add new entries aqui
    c2 = c2.replace("Ã", "A")
    c2 = c2.replace("Õ", "O")
    c2 = c2.replace("Ç", "C")
    c2 = c2.replace("Á", "A")
    c2 = c2.replace("Ó", "O")
    c2 = c2.replace("Ê", "E")
    c2 = c2.replace("É", "E")
    c2 = c2.replace("Ú", "U")

    c = c2.single()
    sb.setCharAt(cont, c)
    cont++

}

return sb.toString()

}

чтобы использовать эти забавы, приведите код так:

     var str: String
     str = editText.text.toString() //get the text from EditText
     str = str.toUpperCase().trim()

     str = stripAccents(str) //call the function
Тиаго Сильва
источник