Есть ли способ избавиться от акцентов и преобразовать целую строку в обычные буквы?
Есть ли лучший способ избавиться от акцентов и сделать эти буквы регулярными, кроме использования String.replaceAll()
метод и замена букв по одному? Пример:
Входные данные: orčpžsíáýd
Выход: orcpzsiayd
Не нужно включать все буквы с акцентами, как русский алфавит или китайский.
15 ответов
использование java.text.Normalizer
чтобы справиться с этим для вас.
string = Normalizer.normalize (string, Normalizer.Form.NFD);
Это отделит все знаки акцента от персонажей. Затем вам просто нужно сравнить каждый символ с буквой и выбросить те, которые не являются.
string = string.replaceAll ("[^ \\ p {ASCII}]", "");
Если ваш текст в Unicode, вы должны использовать это вместо:
string = string.replaceAll ("\\ p {M}", "");
Для юникода, \\P{M}
соответствует основному глифу и \\p{M}
(нижний регистр) соответствует каждому акценту.
Спасибо GarretWilson за указатель и регулярно-expressions.info за отличное руководство по юникоду.
Начиная с 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". Это хороший пример ограничений подхода "раздевания".
Решение от @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 на моей машине).
РЕДАКТИРОВАТЬ: Если вы не застряли с 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 мс, в то время как Normalizer делает это за 3,7 с (в 37 раз медленнее). Если ваши потребности в производительности и вы знаете диапазон ввода, это может быть для вас.
Наслаждаться:-)
System.out.println(Normalizer.normalize("àèé", Normalizer.Form.NFD).replaceAll("\\p{InCombiningDiacriticalMarks}+", ""));
работал на меня. Вывод приведенного выше фрагмента дает "aee", что я и хотел, но
System.out.println(Normalizer.normalize("àèé", Normalizer.Form.NFD).replaceAll("[^\\p{ASCII}]", ""));
не сделал никакой замены.
В зависимости от языка, они могут рассматриваться не как акценты (которые изменяют звучание буквы), а как диакритические знаки
https://en.wikipedia.org/wiki/Diacritic
"Боснийский и хорватский имеют символы č, ć,,, š и ž, которые считаются отдельными буквами и перечислены как таковые в словарях и других контекстах, в которых слова перечислены в алфавитном порядке".
Удаление их может по своей сути изменить значение слова или заменить буквы на совершенно другие.
Я столкнулся с той же проблемой, связанной с проверкой равенства строк. Одна из сравниваемых строк имеет код символа 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,
32
83, 97, 109, 112, 108, 101,32
83, 112, 97, 99, 101,32
, 68, 97, 116, 97] S2: [77, 121,-30, -128, -125
83, 97, 109, 112, 108, 101,-30, -128, -125
83, 112, 97, 99, 101,-30, -128, -125
68, 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 CoreLibraries 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);
Тестирование String и выводов на разных подходах, таких как ➩ 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");
}
}
Поскольку это решение уже доступно в StringUtils.stripAccents() в репозитории Maven и работает для Ł, как упоминалось @DavidS. Поскольку мне нужно, чтобы это работало как для Ø, так и для Ł, изменено, как показано ниже. Может быть, помощь полная для других тоже.
public static String stripAccents(final String input) {
if (input == null) {
return null;
}
final StringBuilder decomposed = new StringBuilder(Normalizer.normalize(input, Normalizer.Form.NFD));
for (int i = 0; i < decomposed.length(); i++) {
if (decomposed.charAt(i) == '\u0141') {
decomposed.setCharAt(i, 'L');
} else if (decomposed.charAt(i) == '\u0142') {
decomposed.setCharAt(i, 'l');
}else if (decomposed.charAt(i) == '\u00D8') {
decomposed.setCharAt(i, 'O');
}else if (decomposed.charAt(i) == '\u00F8') {
decomposed.setCharAt(i, 'o');
}
}
// Note that this doesn't correctly remove ligatures...
return Pattern.compile("\\p{InCombiningDiacriticalMarks}+").matcher(decomposed).replaceAll("");
}
Строка ввода
Ł Tĥïŝ ĩš â fůňķŷ Šťŕĭńġ Ø ø
выходная строка
L This is a funky String O o
Я предлагаю Junidecode. Он будет обрабатывать не только "Ł" и "Ø", но также хорошо работает для транскрибирования с других алфавитов, таких как китайский, в латинский алфавит.
Один из лучших способов использования регулярных выражений и нормализатора, если у вас нет библиотеки, это:
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}.
С уважением.
Решение @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://stackru.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);
}
Столкнулся с той же проблемой, вот решение с использованием расширения Kotlin
val String.stripAccents: String
get() = Regex("\\p{InCombiningDiacriticalMarks}+")
.replace(
Normalizer.normalize(this, Normalizer.Form.NFD),
""
)
Применение
val textWithoutAccents = "some accented string".stripAccents
Я думаю, что лучшее решение - преобразовать каждый символ в HEX и заменить его другим HEX. Это потому, что существует 2 типа Unicode:
Composite Unicode
Precomposed Unicode
Например, "Ồ", написанное составным Unicode, отличается от "Ồ", написанное Precomposed Unicode. Вы можете скопировать мои образцы символов и преобразовать их, чтобы увидеть разницу.
In Composite Unicode, "Ồ" is combined from 2 char: Ô (U+00d4) and ̀ (U+0300)
In Precomposed Unicode, "Ồ" is single char (U+1ED2)
Я разработал эту функцию для некоторых банков, чтобы преобразовать информацию перед ее отправкой в основной банк (обычно не поддерживают Unicode), и столкнулся с этой проблемой, когда конечные пользователи используют несколько типов Unicode для ввода данных. Поэтому я считаю, что переход на HEX и его замена - самый надежный способ.
Быстрый и безопасный способ
public static String removeDiacritics(String str) {
if (str == null)
return null;
if (str.isEmpty())
return "";
int len = str.length();
StringBuilder sb
= new StringBuilder(len);
//iterate string codepoints
for (int i = 0; i < len; ) {
int codePoint = str.codePointAt(i);
int charCount
= Character.charCount(codePoint);
if (charCount > 1) {
for (int j = 0; j < charCount; j++)
sb.append(str.charAt(i + j));
i += charCount;
continue;
}
else if (codePoint <= 127) {
sb.append((char)codePoint);
i++;
continue;
}
sb.append(
java.text.Normalizer
.normalize(
Character.toString((char)codePoint),
java.text.Normalizer.Form.NFD)
.charAt(0));
i++;
}
return sb.toString();
}
Если кто-то изо всех сил пытается сделать это в 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