Есть ли способ избавиться от акцентов и преобразовать целую строку в обычные буквы? - PullRequest
224 голосов
/ 24 июля 2010

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

Ввод: orčpžsíáýd

Выход: orcpzsiayd

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

Ответы [ 10 ]

349 голосов
/ 24 июля 2010

Используйте 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 за указатель и регулярные-выражения.info за отличный гид по юникоду.

114 голосов
/ 06 января 2015

С 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 также не работает для Ø, но он работает для does.Прочитав статью Википедии для Ø , я не уверен, что ее следует заменить на «O»: это отдельная буква на норвежском и датском языках в алфавитном порядке после «z».Это хороший пример ограничений подхода «раздевания».

49 голосов
/ 04 марта 2013

Решение от @ 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);
}

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

27 голосов
/ 31 мая 2012

РЕДАКТИРОВАТЬ: Если вы не застряли с Java <6, и скорость не критична, и / или таблица перевода слишком ограничена, используйте ответ Дэвида. Смысл в том, чтобы использовать <code>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 1 миллион раз за ~ 100 мс, в то время как нормализатор делает это за 3,7 с (медленнее в 37 раз). Если ваши потребности в производительности и вы знаете диапазон ввода, это может быть для вас.

Наслаждайтесь: -)

20 голосов
/ 19 ноября 2010
System.out.println(Normalizer.normalize("àèé", Normalizer.Form.NFD).replaceAll("\\p{InCombiningDiacriticalMarks}+", ""));

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

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

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

6 голосов
/ 24 июля 2010

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

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

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

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

3 голосов
/ 13 ноября 2017

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

2 голосов
/ 08 сентября 2017

Я столкнулся с той же проблемой, связанной с проверкой равенства строк. Одна из сравниваемых строк имеет код символа 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();
}
  • ➩ 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, которые описаны в Стандартном приложении Unicode # 15 - Формы нормализации Unicode и два метода доступа к ним.

    enter image description here

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

Тестирование строки и выводов при различных подходах, таких как ide 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");
    }

}
2 голосов
/ 09 июля 2015

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

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 голос
/ 13 декабря 2018

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

    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}.

Привет.

...