アクセントを取り除き、文字列全体を通常の文字に変換する方法はありますか?


263

String.replaceAll()メソッドを使用したり、文字を1つずつ置き換えたりする以外に、アクセントを取り除き、それらの文字を規則的にするためのより良い方法はありますか?例:

入力: 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}", "");

unicodeの場合\\P{M}、ベースグリフに一致し、\\p{M}(小文字)は各アクセントに一致します。

ポインタを提供してくれたGarretWilsonと、優れたUnicodeガイドを提供してくれたregular-expressions.infoに感謝します。


7
これは毎回正規表現をコンパイルします。1回だけ必要な場合は問題ありませんが、大量のテキストを使用してこれを行う必要がある場合は、正規表現を事前にコンパイルすることをお勧めします。
David Conrad

3
すべてのラテン語ベースの文字がASCII +アクセントに分解されるわけではないことに注意してください。これは例えば殺します。ポーランド語で使用される「ストローク付きラテン語{大文字、小文字}文字l」。
のMichałPolitowski

12
これは良い方法ですが、ASCII以外の文字をすべて削除するのはやり過ぎです。すべてのUnicode「マーク」を削除することをお勧めします。非間隔マーク、間隔/結合マーク、囲みマークを含みます。これはで行えますstring.replaceAll("\\p{M}", "")。詳細については、regular-expressions.info / unicode.htmlを参照してください。
Garret Wilson

4
おそらくNFDではなくNormalizer.Form.NFKDを使用する必要があります-NFKDは合字などをASCII文字(たとえば、fiからfi)に変換しますが、NFDはこれを行いません。
chesterm8 2017

2
@ chesterm8、興味深いことに、NFKDは「fi」を「fi」に変換していますが、「Æ」を「AE」に変換していません。理由を見つけるためにUnicodeデータを表示する必要があると思いますが、それは私が期待したものではありませんでした。
Garret Wilson、

136

2011年以降、Apache Commons StringUtils.stripAccents(input)を使用できます(3.0以降):

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

注意:

受け入れられた回答(Erick Robertson's)は、Øまたはforでは機能しません。Apache Commons 3.5はØでも機能しませんが、Łでは機能します。WikipediaのØに関する記事を読んだ後、「O」に置き換える必要があるかどうかはわかりません。これは、ノルウェー語とデンマーク語の別の文字で、「z」の後にアルファベット順になっています。これは、「ストリップアクセント」アプローチの制限の良い例です。


2
Łの @KarolSに未解決のバグレポートがあるようです。誰かがプルリクエストを送信しましたが、いくつかのテストに失敗し、昨年の7月以降更新されていません。
DavidS 2016

1
5日前に更新があり、プルリクエストがマージされました。
EpicPandaForce 2016年

6
Commons Lang 3.5は数日前にリリースされました。Łで動作することを確認しました。Øでは機能しません。Øに関するWikiの記事を読んでも、「O」に置き換える必要があるかわかりません。これは、ノルウェー語とデンマーク語の別々の文字で、「z」の後にアルファベット順に並んでいます。これは、「ストリップアクセント」アプローチの制限の良い例です。
DavidS 2016年

2
ライブラリを含めたくない場合は、commons.apache.org
proper /

2
デーンとして、デンマーク語/ノルウェー語øはフランス語œと同じで、ドイツ語/スウェーデン語/ハンガリー語/エストニア語などöは、oeを書く短い方法として始まります。したがって、目的によっては、これが必要な代替になる場合があります。
Ole VV

57

@ virgo47によるソリューションは非常に高速ですが、概算です。受け入れられた答えはノーマライザと正規表現を使用しています。正規表現を使用せずにすべての非ASCII文字を削除できるため、Normalizerと正規表現で時間のどの部分が費やされたのか疑問に思いました。

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倍速く、@ virgo47の6.6倍から7倍遅くなっています(受け入れられた回答は、私のマシンの@ virgo47の約26倍遅いです)。


2
outj文字列オブジェクトの作成に使用する前に、有効な文字数に合わせてサイズを変更する必要があります。
レフテリスE

4
私はこの解決策に反対します。入力 "æøåá"を想像してください。Current flattenToAsciiは結果 "aa .."を作成します。ここで、ドットは\ u0000を表します。それは良くない。最初の質問は、「正規化できない」文字をどのように表すかです。それが?になるとしましょう、またはそこにNULL文字を残すことができますが、いずれの場合も(正規表現ソリューションのように)これらの正しい位置を保持する必要があります。このため、ループ内のifは次のようなものif (c <= '\u007F') out[j++] = c; else if (Character.isLetter(c)) out[j++] = '?';でなければなりません。少し遅くなりますが、そもそも正しいはずです。;-)
virgo47 2015

私の最後のコメントを広告します(長すぎることはありません)-ポジティブテイク(isLetter)は適切なものではないかもしれませんが、私はそれを見つけられませんでした。私はUnicodeの専門家ではないので、元の文字を置き換える単一の文字のクラスを特定する方法がわかりません。文字はほとんどのアプリケーション/使用法で問題なく機能します。
virgo47

1
おそらくNFDではなくNormalizer.Form.NFKDを使用する必要があります-NFKDは合字などをASCII文字(たとえば、fiからfi)に変換しますが、NFDはこれを行いません。
chesterm8 2017

2
私たちにとって、キャラクターを完全に削除したかったのです。後続のnull文字がないことを確認するために、代替のStringコンストラクターを使用して削除しました。return new String(out、0、j);
マイクサマラス2018

30

編集:Java <6に悩まされておらず、速度が重要でない場合や変換テーブルの制限が多すぎる場合は、Davidの回答を使用してください。ポイントは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);
}

32ビットJDKを使用した私のハードウェアでのテストでは、Normalizerの方法で3.7秒(37倍遅い)の間に100ミリ秒でàèéàšťč89FDČからaeelstc89FDCへの変換が100万回実行されることが示されています。あなたのニーズがパフォーマンスに関連していて、入力範囲がわかっている場合、これはあなたのためかもしれません。

楽しい :-)


1
提案されたバージョンの速度低下の多くは、正規化ではなく正規表現によるものです。ノーマライザーを使用するが、ASCII以外の文字を「手動で」削除する方が速いですが、バージョンほど速くはありません。しかし、latin1とlatin2だけでなく、すべてのUnicodeで機能します。
デビッドコンラッド

私はより多くの文字、との仕事にこれを拡大しpastebin.com/FAAm6a2j、そのようなDŽ(DZ)としてmultichar文字で正しく動作しません。それから1文字だけを生成します。また、私の関数は文字列の代わりにcharを使用します。これは、とにかくcharを処理している場合はより高速であるため、変換する必要はありません。
James T

ちょっと私はtab00c0フィールドのそれらの文字が何を表すのか理解できませんか?たとえば、「AAAAAAACEEEEIIII」や「lLlNnNnNnnNnOoOo」などです。これまでに見たことはありません。どこで見つけましたか?また、なぜ対応するコードを使用しないのですか?
ThanosFisherman 2014

@ThanosFは、コードを実行してみます(必要に応じてデバッガーを使用)。これは、文字列内のすべての文字に対して行われます。「この文字は\ u00c0と\ u017fの間にありますか?その場合、テーブルの7ビットASCII文字に置き換えてください。」表では、2つのエンコードページ(ラテン1および2)と7ビット相当のページのみをカバーしています。したがって、コードが\ u00e0(à)の文字である場合、テーブルの32番目の位置(e0-c0 = 32)から7ビットの近似値を取得します。これは「a」です。一部の文字は文字ではなく、コードとともに残されています。
virgo47 14

ご説明ありがとうございます。この変数を私の言語に拡張できるように、これらのエンコードページはどこにありますか?(ギリシャ語)受け入れられた回答は既にギリシャ語のアクセント付き文字を置き換える仕事をしていますが、私もあなたの方法を試し、いくつかのベンチマークを実行したいと思いました:)
ThanosFisherman '12

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)でこの問題が発生しました。他のセットアップでは確認できず、その相互関係を確認できませんが、他の提案されたソリューションが機能していることを確認できます。そのため、このソリューションに投票します。:-)(BTW:いくつかの置換を行いましたが、十分ではありませんでした。たとえば、ÚをUに変更しましたが、áをaに変更しませんでした。)
virgo47

1
おそらくNFDではなくNormalizer.Form.NFKDを使用する必要があります-NFKDは合字などをASCII文字(たとえば、fiからfi)に変換しますが、NFDはこれを行いません。
chesterm8 2017

@KarolSどちらのアクセント
eis

@eis文字全体のスラッシュは発音区別符号としてカウントされます:en.wikipedia.org/wiki/Diacriticそして、そのWikipediaページのように「アクセント」のより厳密な定義を使用すると、アクセント記号はアクセントにならないので、Nicoの回答まだ間違っています。
Karol S

6

言語によっては、アクセント(文字の音を変える)とは見なされない場合がありますが、発音区別符号

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

「ボスニア語とクロアチア語には記号č、ć、đ、š、およびhaveがあり、これらは別々の文字と見なされ、辞書および単語がアルファベット順にリストされている他のコンテキストでそのようにリストされています。」

それらを削除すると、本質的に単語の意味が変わったり、文字が完全に異なるものに変わったりすることがあります。


5
同意した。たとえば、スウェーデン語の場合: "höra"(hear)-> "hora"(whore)
ChristofferHammarströmOct

14
彼らが何を意味するかは問題ではありません。問題は、それらを削除する方法です。
エリックロバートソン

7
エリック:何と呼ばれるかが重要です。質問がアクセントを削除する方法を尋ね、それらがアクセントでない場合、答えはアクセントのように見えるものすべてを削除する方法だけではないかもしれません。これはおそらくコメントではなく、答えであるべきです。
2013年

4
これの通常の使用例は、特に英語のキーボードを入力として使用する検索、特に混合言語の検索であると思います。その場合、誤検出よりも誤検出を取得する方が適切です。
nilskp 2014

3

文字列の等価性チェックに関連する同じ問題に直面しました。比較する文字列の1つに 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、3283、112、97、99、101、3268、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();
}
  • Java Java用のUnicode文字列のASCII文字変換。 unidecode

    String initials = Unidecode.decode( s2 );
  • ➩使用Guava:GoogleのコアをLibraries for Java

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

    スペースの URLエンコードには、 Guavaライブラリを使用します。

    String encodedString = UrlEscapers.urlFragmentEscaper().escape(inputString);
  • some 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 java.text.Normalizer.Formを使用する。この列挙型は、Unicode Standard Annex#15 — Unicode正規化フォームとそれらにアクセスするための2つのメソッドで説明されている4つのUnicode正規化フォームの定数を提供します。

    ここに画像の説明を入力してください

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

}

3

Junidecodeをお勧めします。'Ł'と 'Ø'だけでなく、中国語などの他のアルファベットからラテンアルファベットへの文字変換にも有効です。


1
有望に見えますが、これがよりアクティブで維持されたプロジェクトであり、Mavenで利用できることを願っています。
Phil

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

ライブラリがない場合に正規表現とノーマライザを使用する最良の方法の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)

一部の銀行がコアバンクに送信する前に情報を変換するためにこの機能を開発しました(通常はUnicodeをサポートしていません)。エンドユーザーが複数のUnicode入力を使用してデータを入力するときにこの問題に直面しました。したがって、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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.