Чи є спосіб позбутися від наголосів і перетворити цілий рядок у звичайні букви?


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

Якщо ваш текст є унікодом, вам слід скористатися цим:

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

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

Дякуємо GarretWilson за вказівник та regular-expressions.info за чудовий посібник з унікодом .


7
Це збирає регулярний вираз кожен раз, що чудово, якщо він вам потрібен лише один раз, але якщо вам потрібно зробити це з великою кількістю тексту, попереднє складання регулярного вираження є виграшним.
Девід Конрад

3
Зауважте, що не всі літери на латинській основі розкладаються на наголоси ASCII +. Це вб'є, наприклад. "Латинська {велика, мала} літера l із штрихом", що використовується польською мовою.
Michał Politowski

12
Це хороший підхід, але видалення всіх символів, що не належать до ASCII, є надмірним і, ймовірно, видалить речі, які вам не потрібні, як показали інші. Було б краще видалити всі "позначки" Unicode; включаючи позначки без проміжків, пробіли / комбінування знаків та знаки, що додаються. Ви можете це зробити за допомогою string.replaceAll("\\p{M}", ""). Додаткову інформацію див. У розділі regular-expressions.info/unicode.html .
Гаррет Вілсон

4
Ви, мабуть, хочете використовувати Normalizer.Form.NFKD, а не NFD - NFKD перетворить такі речі, як лігатури, в символи ascii (наприклад, fi в fi), NFD цього не зробить.
chesterm8

2
@ chesterm8, що цікаво, NFKD перетворює "fi" у "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
Я бачу, є відкритий звіт про помилку для is, @KarolS. Хтось подав запит на витяг, але він не вдався до деяких тестів і не оновлювався з липня минулого року.
DavidS

1
Там було оновлено 5 днів тому, і запит на витяг був об’єднаний.
EpicPandaForce

6
Commons Lang 3.5 вийшов кілька днів тому. Я підтвердив, що він працює на Ł зараз. Він не працює на Ø. Читаючи статтю Wiki про Ø , я не впевнений, що її слід замінити на "O": це окрема літера норвезькою та датською мовами, що надписом після "z". Це хороший приклад обмежень підходу "наголоси на смужку".
DavidS

2
Якщо ви не хочете включати бібліотеку, ви можете легко скористатись двома способами, які беруть участь у цій функції, з джерела на веб-
lujop

2
Як датчанин, данський / норвезький ø так само, як французький œ і німецький / шведський / угорський / естонський тощо. Ö бере свій початок як короткий спосіб написання о. Тож залежно від вашої мети це може бути заміна, яку ви хочете.
Оле В. В.

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 [], а не зателефонувавши доCharArray (), хоча я не впевнений, що зменшення чіткості коду цього заслуговує:

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.6x до 7 разів повільніше, ніж @ virgo47 (прийнята відповідь приблизно на 26 разів повільніше, ніж у @ virgo47's на моїй машині).


2
outнеобхідно змінити розмір, щоб відповідати кількості дійсних символів, jперш ніж він буде використаний для побудови об'єкта рядка.
Лефтерис Е

4
У мене є заперечення проти цього рішення. Уявіть, що вхід "æøåá". Поточний flattenToAsciiстворює результат "aa ..", де точки представляють \ u0000. Це не добре. Перше питання - як представити "ненормалізованих" персонажів? Скажімо, це буде ?, або ми можемо залишити NULL char там, але в будь-якому випадку ми маємо зберегти правильне положення їх (як це робить рішення геджекс). Для цього цикл if у циклі повинен бути чимось на кшталт: if (c <= '\u007F') out[j++] = c; else if (Character.isLetter(c)) out[j++] = '?';Це трохи сповільнить його, але в першу чергу має бути правильним. ;-)
virgo47

Оголошення мого останнього коментаря (дуже погано, що більше не можна) - можливо, позитивний take ( isLetter) не є правильним, але я не знайшов кращого. Я не є експертом Unicode, тому не знаю, як краще визначити клас одного символу, який замінює оригінальний символ. Листи справні для більшості програм / звичаїв.
virgo47

1
Ви, мабуть, хочете використовувати Normalizer.Form.NFKD, а не NFD - NFKD перетворить такі речі, як лігатури, в символи ascii (наприклад, fi в fi), NFD цього не зробить.
chesterm8

2
Для нас ми хотіли повністю зняти персонажа. Щоб переконатись у відсутності нульових символів, я видалив їх за допомогою альтернативного конструктора String: поверніть новий String (out, 0, j);
Майк Самарас

30

EDIT: Якщо ви не зациклювалися на 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 показують, що це здійснює перетворення з àèéľšťč89FDČ в aeelstc89FDC в 1 мільйон разів за ~ 100 мс, тоді як нормалізатор робить це в 3,7 секунди (на 37 разів повільніше). У випадку, якщо ваші потреби не відрізняються ефективністю, і ви знаєте діапазон введення, це може бути для вас.

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


1
Багато сповільненості запропонованої версії пояснюється регулярним виразом, а не нормалізатором. Використання Normalizer, але видалення символів, що не належать до ASCII, "вручну" - це швидше, хоча все ще не так швидко, як ваша версія. Але він працює для всіх Unicode, а не лише для latin1 та latin2.
Девід Конрад

Я розширив це, щоб працювати з більшою кількістю символів, pastebin.com/FAAm6a2j . Він створить з нього лише 1 символ. Також моя функція використовує char замість рядків, що швидше, якщо ви все одно обробляєте char, тому вам не доведеться конвертувати.
Джеймс Т

Гей, я не розумію, для чого стоять ті літери в полі tab00c0? наприклад, "AAAAAAACEEEEIIII" або "lLlNnNnNnnNnOoOo" тощо. Ніколи раніше їх не бачив. Де ви їх знайшли? Крім того, чому ви просто не використовуєте відповідні коди?
ThanosFisherman

@ThanosF просто спробуйте пройти код (за потреби відладчик). Що це робить для кожного символу в рядку: "Чи є цей символ між \ u00c0 та \ u017f? Якщо так, замініть його 7-бітовим символом ASCII з таблиці." Таблиця просто охоплює дві сторінки кодування (латинська 1 та 2) з їх 7-бітовими еквівалентами. Отже, якщо це символ з кодом \ u00e0 (à), він буде приймати 7-бітове наближення з 32-го положення таблиці (e0-c0 = 32) - це "a". Деякі символи не букви, вони залишаються зі своїм кодом.
virgo47

Дякуємо за ваше пояснення. Де я можу знайти ці сторінки кодування, щоб я міг поширити цю Змінну на свою мову? (Грецька) Прийнята відповідь вже виконує завдання заміни букв із наголосом на грецькій мові, але я також хотів спробувати ваш метод і запустити деякі орієнтири :)
ThanosFisherman

22
System.out.println(Normalizer.normalize("àèé", Normalizer.Form.NFD).replaceAll("\\p{InCombiningDiacriticalMarks}+", ""));

працював на мене. Вихід фрагмента вище дає "ае", що я хотів, але

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

не робив жодної заміни.


1
Підтверджуючи це ... як правило, ASCII працює чудово, але я зіткнувся з цією проблемою в Linux (64b) з JRockit (1.6.0_29 64b). Неможливо підтвердити це за допомогою будь-якої іншої установки, не можу підтвердити співвіднесення, але я можу підтвердити, що інше запропоноване рішення спрацювало, і за це я голосую за це. :-) (BTW: Це зробило деяку заміну, але недостатньо, вона змінила Ú на U, наприклад, але не на á.)
virgo47

1
Ви, мабуть, хочете використовувати Normalizer.Form.NFKD, а не NFD - NFKD перетворить такі речі, як лігатури, в символи ascii (наприклад, fi в fi), NFD цього не зробить.
chesterm8

@KarolS я не бачу будь-якого з них , що містять будь - яких акценти
EIS

@eis Нахил на письмі вважається діакритичним: en.wikipedia.org/wiki/Diacritic І якщо ви будете чіткіше визначати "акцент", як на цій сторінці Вікіпедії, то діарез не є акцентом, тому відповідь Ніко все ще помиляється.
Karol S

6

Залежно від мови, це можуть вважатися не наголосами (які змінюють звучання букви), а діакритичними позначками

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

"У боснійській та хорватській мовах є символи č, ć, đ, š і ž, які вважаються окремими літерами і перераховані як такі в словниках та інших контекстах, у яких слова перераховані за алфавітом".

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


5
Домовились. Наприклад на шведській мові: "höra" (чути) -> "hora" (шлюха)
Christoffer Hammarström

14
Не має значення, що вони означають. Питання в тому, як їх видалити.
Ерік Робертсон

7
Ерік: Має значення те, як їх називають. Якщо питання задає питання про те, як видалити наголоси, а якщо це не наголоси, то відповідь може бути не просто тим, як видалити всі ті речі, схожі на акценти. Хоча це, мабуть, має бути коментарем, а не відповіддю.
Сміг

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, -12583, 97, 109, 112, 108, 101,, -30, -128, -12583, 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();
}
  • Trans транслітерації 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 та два способи доступу до них.

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

    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 . Він буде обробляти не тільки "Ł" та "Ø", але також добре працює для транскрибування з інших алфавітів, наприклад китайської, на латинський алфавіт.


1
Це виглядає перспективно, але я б хотів, щоб це був більш активний / підтримуваний проект і доступний у Maven.
Філ

2

Рішення @David Conrad - це найшвидший тест, який я намагався використовувати Normalizer, але в ньому є помилка. Це в основному смужки символів, які не є наголосами, наприклад, китайські символи та інші літери на зразок æ, всі позбавлені. Символи, які ми хочемо зняти, не мають проміжків, символи, які не займають додаткової ширини в остаточному рядку. Ці символи нульової ширини в основному поєднуються з деякими іншими символами. Якщо ви можете бачити їх ізольованими як персонаж, наприклад, подібний `, я гадаю, що він поєднується з символом пробілу.

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

Це більш ефективно, ніж substituAll ("[^ \ p {ASCII}]", ""), і якщо вам не потрібні діакритики (як у вашому прикладі).

В іншому випадку вам доведеться використовувати шаблон {{ASCII}.

З повагою


0

Я думаю, що найкраще рішення - перетворити кожну таблицю на HEX та замінити її іншою HEX. Це тому, що є 2 типи Unicode:

Composite Unicode
Precomposed Unicode

Наприклад, "Ồ", написаний Composite Unicode, відрізняється від "Ồ", написаного попередньо створеним 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 та його заміна - це найнадійніший спосіб.


-1

Якщо хтось бореться за це в котліні, цей код працює як шарм. Щоб уникнути невідповідностей, я також використовую .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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.