C'è un modo per sbarazzarsi degli accenti e convertire un'intera stringa in lettere regolari?


263

C'è un modo migliore per sbarazzarsi degli accenti e rendere quelle lettere regolari oltre all'utilizzo String.replaceAll() metodo e alla sostituzione delle lettere una per una? Esempio:

Ingresso: orčpžsíáýd

Produzione: orcpzsiayd

Non è necessario includere tutte le lettere con accenti come l'alfabeto russo o quello cinese.

Risposte:


387

Utilizzare java.text.Normalizerper gestire questo per te.

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

Questo separerà tutti i segni di accento dai personaggi. Quindi, devi solo confrontare ogni personaggio con l'essere una lettera e scartare quelli che non lo sono.

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

Se il tuo testo è in Unicode, dovresti invece usare questo:

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

Per unicode, \\P{M}corrisponde al glifo base e\\p{M} (minuscolo) corrisponde a ciascun accento.

Grazie a GarretWilson per il puntatore e regular-expressions.info per l'ottima guida unicode.


7
Questo compila ogni volta l'espressione regolare, il che va bene se ne hai bisogno solo una volta, ma se devi farlo con molto testo, pre-compilare la regex è una vittoria.
David Conrad,

3
Nota che non tutte le lettere in latino si decompongono in accenti ASCII +. Questo ucciderà ad es. "Latina {maiuscola} lettera l con tratto" usata in polacco.
Michał Politowski,

12
Questo è un buon approccio, ma rimuovere tutti i caratteri non ASCII è eccessivo e probabilmente rimuoverà le cose che non vuoi, come altri hanno indicato. Sarebbe meglio rimuovere tutti i "segni" Unicode; compresi i segni di non spaziatura, i segni di spaziatura / combinazione e i segni di chiusura. Puoi farlo con string.replaceAll("\\p{M}", ""). Vedi regular-expressions.info/unicode.html per ulteriori informazioni.
Garret Wilson

4
Probabilmente vuoi usare Normalizer.Form.NFKD piuttosto che NFD - NFKD convertirà cose come le legature in caratteri ascii (es. Da fi a fi), NFD non lo farà.
chesterm8,

2
@ chesterm8, è interessante notare che NFKD sta convertendo "fi" in "fi", ma non sta convertendo "Æ" in "AE". Immagino che dovrò far apparire i dati Unicode per scoprire perché, ma non era quello che mi aspettavo.
Garret Wilson,

136

A partire dal 2011 puoi usare Apache Commons StringUtils.stripAccents (input) (dal 3.0):

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

Nota:

La risposta accettata (di Erick Robertson) non funziona per Ø o £. Apache Commons 3.5 non funziona neanche per Ø, ma funziona per £. Dopo aver letto l' articolo di Wikipedia per Ø , non sono sicuro che debba essere sostituito con "O": è una lettera separata in norvegese e danese, in ordine alfabetico dopo "z". È un buon esempio dei limiti dell'approccio "accenti alla striscia".


2
Vedo che esiste una segnalazione di bug aperta per £ , @KarolS. Qualcuno ha inviato una richiesta pull, ma ha fallito alcuni test e non è stato aggiornato dal luglio dello scorso anno.
David

1
Ci sono stati aggiornamenti 5 giorni fa e la richiesta pull è stata unita.
EpicPandaForce,

6
Commons Lang 3.5 è stato rilasciato diversi giorni fa. Ho confermato che funziona su £ ora. Non funziona su Ø. Leggendo l'articolo Wiki per Ø , non sono sicuro che debba essere sostituito con "O": è una lettera separata in norvegese e danese, in ordine alfabetico dopo "z". È un buon esempio dei limiti dell'approccio "accenti alla striscia".
DavidS,

2
Se non vuoi includere la libreria, puoi prendere facilmente i due metodi coinvolti in quella funzione dalla fonte su commons.apache.org/proper/commons-lang/apidocs/src-html/org/…
lujop

2
Come danese, il danese / norvegese ø proprio come il francese œ e il tedesco / svedese / ungherese / estone ecc. Ö nasce come un modo breve per scrivere oe. Quindi, a seconda del tuo scopo, questa potrebbe essere la sostituzione che desideri.
Ole VV,

57

La soluzione di @ virgo47 è molto veloce, ma approssimativa. La risposta accettata utilizza Normalizer e un'espressione regolare. Mi chiedevo quale parte del tempo fosse impiegata da Normalizer rispetto all'espressione regolare, poiché la rimozione di tutti i caratteri non ASCII può essere eseguita senza regex:

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();
    }
}

Piccole accelerazioni aggiuntive possono essere ottenute scrivendo in un carattere [] e non chiamando a CharArray (), anche se non sono sicuro che la diminuzione della chiarezza del codice meriti:

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);
}

Questa variazione ha il vantaggio della correttezza di quella che utilizza Normalizer e della velocità di quella che utilizza una tabella. Sulla mia macchina, questa è circa 4 volte più veloce della risposta accettata e da 6,6 a 7 volte più lenta di quella di @ virgo47 (la risposta accettata è circa 26 volte più lenta di quella di @ virgo47 sulla mia macchina).


2
outdeve essere ridimensionato in modo che corrisponda al numero di caratteri validi jprima di essere utilizzato per costruire l'oggetto stringa.
Lefteris E,

4
Ho un'obiezione a questa soluzione. Immagina input "æøåá". La corrente flattenToAsciicrea il risultato "aa .." dove i punti rappresentano \ u0000. Non è buono. La prima domanda è: come rappresentare i personaggi "non normalizzabili"? Diciamo che sarà? Oppure possiamo lasciare NULL lì, ma in ogni caso dobbiamo preservare la posizione corretta di questi (proprio come fa la soluzione regex). Per questo l'if nel ciclo deve essere qualcosa di simile: if (c <= '\u007F') out[j++] = c; else if (Character.isLetter(c)) out[j++] = '?';rallenterà un po ', ma deve essere corretto in primo luogo. ;-)
virgo47

Ad il mio ultimo commento (peccato che non possano essere più lunghi) - forse il take positivo ( isLetter) non è quello giusto, ma non ho trovato di meglio. Non sono un esperto Unicode, quindi non so come identificare meglio la classe del singolo personaggio che sostituisce il personaggio originale. Le lettere funzionano bene per la maggior parte delle applicazioni / usi.
virgo47,

1
Probabilmente vuoi usare Normalizer.Form.NFKD piuttosto che NFD - NFKD convertirà cose come le legature in caratteri ascii (es. Da fi a fi), NFD non lo farà.
chesterm8,

2
Per noi volevamo rimuovere del tutto il personaggio. Per assicurarmi che non ci fossero caratteri null finali, li ho rimossi con un costruttore String alternativo: return new String (out, 0, j);
Mike Samaras,

30

EDIT: Se non sei bloccato con Java <6 e la velocità non è critica e / o la tabella di traduzione è troppo limitante, usa la risposta di David. Il punto è usare Normalizer(introdotto in Java 6) invece della tabella di traduzione all'interno del ciclo.

Sebbene questa non sia una soluzione "perfetta", funziona bene quando si conosce l'intervallo (nel nostro caso Latin1,2), ha funzionato prima di Java 6 (non è un vero problema) ed è molto più veloce della versione più suggerita (può o può non essere un problema):

    /**
 * 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);
}

I test sul mio HW con JDK a 32 bit mostrano che questo esegue la conversione da àèéľšťč89FDČ a aeelstc89FDC 1 milione di volte in ~ 100ms mentre il modo Normalizer lo fa in 3.7s (37x più lento). Nel caso in cui le tue esigenze siano legate alle prestazioni e conosci la gamma di input, questo potrebbe essere per te.

Godere :-)


1
Molta lentezza della versione suggerita è dovuta all'espressione regolare, non al Normalizer. L'uso di Normalizer ma la rimozione manuale dei caratteri non ASCII è più veloce, anche se non altrettanto veloce della versione. Ma funziona per tutto Unicode invece che solo per latin1 e latin2.
David Conrad,

Ho espanso questo per funzionare con più caratteri, pastebin.com/FAAm6a2j , Nota che non funzionerà correttamente con caratteri multichar come DŽ (DZ). Produrrà solo 1 personaggio da esso. Inoltre la mia funzione usa char invece delle stringhe, che è più veloce SE stai gestendo comunque char, quindi non devi convertirlo.
James T

Ehi, non capisco cosa significano quelle lettere sul campo tab00c0? ad esempio "AAAAAAACEEEEIIII" o "lLlNnNnNnnNnOoOo" ecc. Non li avevo mai visti prima. Dove li hai trovati? Inoltre, perché non usi semplicemente i codici corrispondenti?
ThanosFisherman,

@ThanosF prova solo a leggere il codice (con debugger se necessario). Ciò che fa è per ogni carattere di una stringa: "Questo carattere è compreso tra \ u00c0 e \ u017f? In tal caso, sostituirlo con un carattere ASCII a 7 bit della tabella." La tabella copre solo due pagine di codifica (latino 1 e 2) con i loro equivalenti a 7 bit. Quindi, se è un carattere con codice \ u00e0 (à), prenderà la sua approssimazione a 7 bit dalla 32a posizione della tabella (e0-c0 = 32) - che è "a". Alcuni caratteri non sono lettere, quelli sono lasciati lì con il loro codice.
virgo47,

Grazie per la tua spiegazione. Dove posso trovare quelle pagine di codifica in modo da poter estendere questa variabile alla mia lingua? (Greco) La risposta accettata fa già il lavoro di sostituzione delle lettere accentate in greco, ma volevo provare anche il tuo metodo ed eseguire alcuni benchmark :)
ThanosFisherman

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

ha funzionato per me. L'output dello snippet qui sopra dà "aee" che è quello che volevo, ma

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

non ha fatto alcuna sostituzione.


1
Confermando questo ... normalmente ASCII funziona bene, ma ho riscontrato questo problema su Linux (64b) con JRockit (1.6.0_29 64b). Non riesco a confermarlo con nessun'altra impostazione, non posso confermare quell'incoronazione, ma posso confermare che l'altra soluzione suggerita ha funzionato e per questo voto questa. :-) (A proposito: ha fatto un po 'di sostituzione, ma non abbastanza, ha cambiato Ú in U per esempio, ma non á in a.)
virgo47

1
Probabilmente vuoi usare Normalizer.Form.NFKD piuttosto che NFD - NFKD convertirà cose come le legature in caratteri ascii (es. Da fi a fi), NFD non lo farà.
chesterm8,

@KarolS Non vedo nessuno dei due con alcun accento
eis

@eis Una barra sopra una lettera è considerata diacritica: en.wikipedia.org/wiki/Diacritic E se si passa a una definizione più rigorosa di "accento" come in quella pagina di Wikipedia, allora la diaeresi non è un accento, quindi la risposta di Nico è ancora sbagliato.
Karol S,

6

A seconda della lingua, questi potrebbero non essere considerati accenti (che cambiano il suono della lettera), ma segni diacritici

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

"Bosniaco e croato hanno i simboli č, ć, đ, š e ž, che sono considerati lettere separate e sono elencati come tali nei dizionari e in altri contesti in cui le parole sono elencate secondo l'ordine alfabetico."

Rimuoverli potrebbe cambiare intrinsecamente il significato della parola o cambiare le lettere in parole completamente diverse.


5
Concordato. Ad esempio in svedese: "höra" (ascolta) -> "hora" (puttana)
Christoffer Hammarström,

14
Non importa cosa significano. La domanda è come rimuoverli.
Erick Robertson,

7
Erick: importa come si chiamano. Se la domanda chiede come rimuovere gli accenti e se quelli non sono accenti, la risposta potrebbe non essere proprio come rimuovere tutte quelle cose che sembrano accenti. Anche se questo dovrebbe probabilmente essere un commento e non una risposta.
Smig

4
Penso che il normale caso d'uso sia la ricerca, in particolare la ricerca di lingue miste, spesso con una tastiera inglese come input, nel qual caso è meglio ottenere falsi positivi che falsi negativi.
nilskp,

3

Ho riscontrato lo stesso problema relativo al controllo dell'uguaglianza delle stringhe, una delle stringhe di confronto ha il codice di caratteri ASCII 128-255 .

vale a dire, Spazio non-break - [Hex - A0] Space [Hex - 20]. Per mostrare lo spazio senza interruzioni su HTML. Ho usato il seguente spacing entities. Il loro carattere e i suoi byte sono simili&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()));

Uscita in byte:

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

Utilizzare il codice seguente per diversi spazi e i relativi codici byte: 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();
}
  • ➩ Traslitterazioni ASCII della stringa Unicode per Java. unidecode

    String initials = Unidecode.decode( s2 );
  • ➩ utilizzando Guava: Google Core Libraries for Java.

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

    Per la codifica dell'URL per lo spazio utilizzare la guaiava di Guava.

    String encodedString = UrlEscapers.urlFragmentEscaper().escape(inputString);
  • ➩ Per superare questo problema usato String.replaceAll()con alcuni 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(" ", " ");
  • ➩ Utilizzo di java.text.Normalizer.Form . Questo enum fornisce costanti dei quattro moduli di normalizzazione Unicode descritti nell'Allegato n. 15 Unicode Standard - Moduli di normalizzazione Unicode e due metodi per accedervi.

    inserisci qui la descrizione dell'immagine

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

Test di stringhe e output su diversi approcci come ➩ 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 );

L'uso di Unidecode è il best choicemio codice finale mostrato di seguito.

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

Suggerisco Junidecode . Gestirà non solo 'Ł' e 'Ø', ma funziona anche bene per trascrivere da altri alfabeti, come il cinese, in alfabeto latino.


1
Sembra promettente, ma vorrei che fosse un progetto più attivo / mantenuto e disponibile su Maven.
Phil

2

La soluzione @David Conrad è la più veloce che ho provato a utilizzare Normalizer, ma ha un bug. In pratica spoglia i personaggi che non sono accenti, ad esempio i caratteri cinesi e altre lettere come æ, sono tutti messi a nudo. I caratteri che vogliamo rimuovere sono segni senza spaziatura, caratteri che non occupano una larghezza extra nella stringa finale. Questi caratteri a larghezza zero finiscono sostanzialmente per combinarsi con qualche altro personaggio. Se riesci a vederli isolati come personaggi, ad esempio in questo modo, la mia ipotesi è che sia combinato con il personaggio spaziale.

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

Uno dei modi migliori per utilizzare regex e Normalizer se non si dispone di una libreria è:

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

Questo è più efficiente di replaceAll ("[^ \ p {ASCII}]", "")) e se non hai bisogno di segni diacritici (proprio come nel tuo esempio).

Altrimenti, devi usare il modello p {ASCII}.

Saluti.


0

Penso che la soluzione migliore sia convertire ogni carattere in HEX e sostituirlo con un altro HEX. È perché ci sono 2 digitazioni Unicode:

Composite Unicode
Precomposed Unicode

Ad esempio "Ồ" scritto da Unicode composito è diverso da "Ồ" scritto da Unicode precomposto. Puoi copiare i miei caratteri di esempio e convertirli per vedere la differenza.

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

Ho sviluppato questa funzione per alcune banche per convertire le informazioni prima di inviarle a core-bank (di solito non supportano Unicode) e ho riscontrato questo problema quando gli utenti finali utilizzano più tipi di Unicode per inserire i dati. Quindi penso che convertire in HEX e sostituirlo sia il modo più affidabile.


-1

Nel caso qualcuno stia lottando per farlo in kotlin, questo codice funziona come un incantesimo. Per evitare incoerenze uso anche .toUpperCase e Trim (). poi lancio questa funzione:

   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()

}

per usare questi divertenti cast il codice in questo modo:

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

     str = stripAccents(str) //call the function
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.