Unicode文字から発音区別符号(charǹňñṅņṇṋṉ̈ɲƞᶇɳȵ)を削除する


88

分音記号付きの文字(チルダサーカムフレックスキャレットウムラウトキャロン)とその「単純な」文字との間でマッピングできるアルゴリズムを探しています

例えば:

ń  ǹ  ň  ñ    ņ        ̈  ɲ  ƞ  ɳ ȵ  --> n
á --> a
ä --> a
 --> a
 --> o

等。

  1. 私はこれをJavaで実行したいのですが、Unicodeのようなもので、どの言語でも簡単に実行できるはずです。

  2. 目的:分音記号付きの単語を簡単に検索できるようにします。たとえば、テニスプレーヤーのデータベースがあり、Björn_Borgが入力されている場合は、Bjorn_Borgも保持するので、誰かがBjörnではなくBjornに入った場合にそれを見つけることができます。


プログラミングする環境によって異なりますが、手動で何らかのマッピングテーブルを維持する必要があるでしょう。では、どの言語を使用していますか?
トラリン

15
ñen.wikipedia.org/wiki/ % C3 % 91のような一部の文字は、検索のために発音区別符号を取り除いてはならないことに注意してください。Googleはスペイン語の「ano」(anus)と「año」(year)を正しく区別しています。したがって、本当に優れた検索エンジンが必要な場合は、基本的な発音区別符号の削除に依存することはできません。
Eduardo

@エドゥアルド:重要ではないかもしれない特定のコンテキストで。OPが与えた例を使用して、多国籍のコンテキストで人物の名前を検索する場合、検索が正確すぎないようにする必要があります。
アミールアビリ

(誤って前に送信されました)ただし、発音区別符号を対応する音声にマッピングして、音声検索を改善する余地はあります。つまり、基礎となる検索エンジンが音声ベースの(たとえば、soundex)検索をサポートしている場合、ñ=> niはより良い結果をもたらします
Amir

あの等に枚Añoを変更するのURL、IDの等のための非BASE64の文字ストリッピングされたユースケース
Ondraジシュカ

回答:


82

私は最近これをJavaで行いました:

public static final Pattern DIACRITICS_AND_FRIENDS
    = Pattern.compile("[\\p{InCombiningDiacriticalMarks}\\p{IsLm}\\p{IsSk}]+");

private static String stripDiacritics(String str) {
    str = Normalizer.normalize(str, Normalizer.Form.NFD);
    str = DIACRITICS_AND_FRIENDS.matcher(str).replaceAll("");
    return str;
}

これは、指定したとおりに行われます。

stripDiacritics("Björn")  = Bjorn

ただし、ł文字が発音区別符号を持たないため、たとえばビャウィストクでは失敗します。

本格的な文字列簡略化を使用する場合は、発音区別符号ではないいくつかの特殊文字のために、2回目のクリーンアップラウンドが必要になります。このマップは、お客様の名前に表示される最も一般的な特殊文字を含んでいます。これは完全なリストではありませんが、それを拡張する方法のアイデアを提供します。immutableMapは、google-collectionsの単純なクラスです。

public class StringSimplifier {
    public static final char DEFAULT_REPLACE_CHAR = '-';
    public static final String DEFAULT_REPLACE = String.valueOf(DEFAULT_REPLACE_CHAR);
    private static final ImmutableMap<String, String> NONDIACRITICS = ImmutableMap.<String, String>builder()

        //Remove crap strings with no sematics
        .put(".", "")
        .put("\"", "")
        .put("'", "")

        //Keep relevant characters as seperation
        .put(" ", DEFAULT_REPLACE)
        .put("]", DEFAULT_REPLACE)
        .put("[", DEFAULT_REPLACE)
        .put(")", DEFAULT_REPLACE)
        .put("(", DEFAULT_REPLACE)
        .put("=", DEFAULT_REPLACE)
        .put("!", DEFAULT_REPLACE)
        .put("/", DEFAULT_REPLACE)
        .put("\\", DEFAULT_REPLACE)
        .put("&", DEFAULT_REPLACE)
        .put(",", DEFAULT_REPLACE)
        .put("?", DEFAULT_REPLACE)
        .put("°", DEFAULT_REPLACE) //Remove ?? is diacritic?
        .put("|", DEFAULT_REPLACE)
        .put("<", DEFAULT_REPLACE)
        .put(">", DEFAULT_REPLACE)
        .put(";", DEFAULT_REPLACE)
        .put(":", DEFAULT_REPLACE)
        .put("_", DEFAULT_REPLACE)
        .put("#", DEFAULT_REPLACE)
        .put("~", DEFAULT_REPLACE)
        .put("+", DEFAULT_REPLACE)
        .put("*", DEFAULT_REPLACE)

        //Replace non-diacritics as their equivalent characters
        .put("\u0141", "l") // BiaLystock
        .put("\u0142", "l") // Bialystock
        .put("ß", "ss")
        .put("æ", "ae")
        .put("ø", "o")
        .put("©", "c")
        .put("\u00D0", "d") // All Ð ð from http://de.wikipedia.org/wiki/%C3%90
        .put("\u00F0", "d")
        .put("\u0110", "d")
        .put("\u0111", "d")
        .put("\u0189", "d")
        .put("\u0256", "d")
        .put("\u00DE", "th") // thorn Þ
        .put("\u00FE", "th") // thorn þ
        .build();


    public static String simplifiedString(String orig) {
        String str = orig;
        if (str == null) {
            return null;
        }
        str = stripDiacritics(str);
        str = stripNonDiacritics(str);
        if (str.length() == 0) {
            // Ugly special case to work around non-existing empty strings
            // in Oracle. Store original crapstring as simplified.
            // It would return an empty string if Oracle could store it.
            return orig;
        }
        return str.toLowerCase();
    }

    private static String stripNonDiacritics(String orig) {
        StringBuffer ret = new StringBuffer();
        String lastchar = null;
        for (int i = 0; i < orig.length(); i++) {
            String source = orig.substring(i, i + 1);
            String replace = NONDIACRITICS.get(source);
            String toReplace = replace == null ? String.valueOf(source) : replace;
            if (DEFAULT_REPLACE.equals(lastchar) && DEFAULT_REPLACE.equals(toReplace)) {
                toReplace = "";
            } else {
                lastchar = toReplace;
            }
            ret.append(toReplace);
        }
        if (ret.length() > 0 && DEFAULT_REPLACE_CHAR == ret.charAt(ret.length() - 1)) {
            ret.deleteCharAt(ret.length() - 1);
        }
        return ret.toString();
    }

    /*
    Special regular expression character ranges relevant for simplification -> see http://docstore.mik.ua/orelly/perl/prog3/ch05_04.htm
    InCombiningDiacriticalMarks: special marks that are part of "normal" ä, ö, î etc..
        IsSk: Symbol, Modifier see http://www.fileformat.info/info/unicode/category/Sk/list.htm
        IsLm: Letter, Modifier see http://www.fileformat.info/info/unicode/category/Lm/list.htm
     */
    public static final Pattern DIACRITICS_AND_FRIENDS
        = Pattern.compile("[\\p{InCombiningDiacriticalMarks}\\p{IsLm}\\p{IsSk}]+");


    private static String stripDiacritics(String str) {
        str = Normalizer.normalize(str, Normalizer.Form.NFD);
        str = DIACRITICS_AND_FRIENDS.matcher(str).replaceAll("");
        return str;
    }
}

╨のような文字はどうですか?
mickthompson

彼らは通過します。同様に、すべての日本語の文字など
Andreas Petersson

アンドレアスに感謝します。これらを削除する方法はありますか?らがなを覚男(またはその他)のような文字が生成された文字列に含まれ、これらは基本的に出力を壊します。StackOverflowが質問のURLに対して行うように、URLジェネレーターとして簡略化された文字列の出力を使用しようとしています。
ミクトンプソン

2
質問のコメントで言ったように。優れた検索エンジンが必要な場合は、基本的な発音区別符号の削除に依存することはできません。
エドゥアルド

3
アンドレアスに感謝します、魅力のように働きます!:-)(rrrr̈r'ŕřttẗţỳỹẙy'yýÿŷpp̈sss̈s̊s's̸śŝŞşšddd̈ďd'ḑf̈f̸ggg̈g'ģqĝǧḧĥj̈j'ḱkk̈k̸ǩlll̈Łłẅẍcc̈c̊c'c̸Çççćĉčvv̈v'v̸bb̧ǹnn̈n̊n'ńņňñmmmm̈m̊m̌ǵß上でテスト)
Fortega

24

コアjava.textパッケージは、この使用例(発音区別符号、大文字小文字などを気にせずに文字列を一致させる)に対処するように設計されました。

文字の違いでCollatorソートするようにを設定しますPRIMARY。それで、CollationKey文字列ごとにを作成します。すべてのコードがJavaである場合は、CollationKey直接使用できます。キーをデータベースまたはその他の種類のインデックスに格納する必要がある場合は、それをバイト配列に変換できます。

これらのクラスは、Unicode標準の大文字と小文字を区別するデータを使用して、同等の文字を判別し、さまざまな分解戦略をサポートします。

Collator c = Collator.getInstance();
c.setStrength(Collator.PRIMARY);
Map<CollationKey, String> dictionary = new TreeMap<CollationKey, String>();
dictionary.put(c.getCollationKey("Björn"), "Björn");
...
CollationKey query = c.getCollationKey("bjorn");
System.out.println(dictionary.get(query)); // --> "Björn"

コレーターはロケール固有であることに注意してください。これは、「アルファベット順」がロケール間で異なるためです(スペイン語の場合のように、時間の経過によっても異なります)。Collatorクラスは、これらの規則のすべてを追跡し、現在までにそれらを維持することからあなたを解放します。


興味深いように聞こえますが、データベースで照合キーを検索するには、select * from person where collat​​ed_name like 'bjo%' ??
Andreas Petersson 09/09/21

とてもいいです、それについて知りませんでした。これを試してみます。
Andreas Petersson

Androidでは、Collat​​ionKeysをデータベース検索のプレフィックスとして使用できません。文字列の照合キーはaバイト41、1、5、1、5、0になりますが、文字列はバイト41、43、1、6、1、6、0にabなります。これらのバイトシーケンスはそのままでは表示されません完全な言葉で(照合キーaのバイト配列がの照合キーのバイト配列に表示されないab
Grzegorz Adam Hankiewicz

1
@GrzegorzAdamHankiewiczいくつかのテストの結果、バイト配列を比較できることがわかりましたが、ご指摘のとおり、プレフィックスは形成していません。したがって、のようなプレフィックスクエリbjo%を実行するには、コレーターが> = bjoおよび< bjp(またはそのロケールの次の記号が何であれ、それをプログラムで決定する方法がない)である範囲クエリを実行する必要があります。
エリクソン


12

以下のNormalizerクラスを使用できますjava.text

System.out.println(new String(Normalizer.normalize("ń ǹ ň ñ ṅ ņ ṇ ṋ", Normalizer.Form.NFKD).getBytes("ascii"), "ascii"));

しかし、Javaは変換不可能なUnicode文字で奇妙なことをするので、やらなければならないことがまだあります(無視されず、例外もスローされません)。しかし、それを出発点として使用できると思います。


3
これは、ロシア語などのASCII以外の発音区別符号では機能しません。また、発音区別符号もあり、さらにすべてのアジア文字列を屠殺します。使ってはいけません。代わりに、応答のようASCIIに、使用\\ P {InCombiningDiacriticalMarks}正規表現に変換するstackoverflow.com/questions/1453171/...
アンドレアス・ピーターソン

10

ユニコードのウェブサイトには、関連する資料がたくさんあるキャラクター折りたたみのドラフトレポートがあります。特にセクション4.1を参照してください。「折りたたみアルゴリズム」。

ここでは、Perlを使用した分音記号マーカーの削除の説明と実装を示します。

これらの既存のSOの質問は関連しています。


4

これらのマークのすべてが一部の「通常」の文字の「マーク」であるだけではなく、意味を変更せずに削除できることに注意してください。

スウェーデン語では、åäとöは真の適切なファーストクラスのキャラクターであり、他のキャラクターの「バリアント」ではありません。他のすべての文字とは音が異なり、並べ替えが異なり、単語の意味を変えます( "mätt"と "matt"は2つの異なる単語です)。


4
正解ですが、これは質問に対する回答というよりはコメントです。
Simon Forsberg、2013

2

Unicodeには特定のdiatric文字(複合文字)があり、文字列を変換して文字とdiatricsを分離できます。次に、文字列から分音記号を削除するだけで、基本的には完了です。

正規化、分解、および同等性の詳細については、UnicodeホームページのUnicode Standardを参照してください

ただし、実際にこれをどのように達成できるかは、フレームワーク/ OS / ...作業している内容によって異なります。.NETを使用している場合は、System.Text.NormalizationForm列挙を受け入れるString.Normalizeメソッドを使用できます。


2
これは.NETで使用する方法ですが、一部の文字を手動でマップする必要があります。それらは発音区別符号ではなく、有向グラフです。しかし、同様の問題。
トラリン

1
正規化形式 "D"(つまり分解)に変換し、ベース文字を取得します。
リチャード

2

(私にとって)最も簡単な方法は、Unicodeコードポイントを表示可能な文字列に変更するスパースマッピング配列を維持することです。

といった:

start    = 0x00C0
size     = 23
mappings = {
    "A","A","A","A","A","A","AE","C",
    "E","E","E","E","I","I","I", "I",
    "D","N","O","O","O","O","O"
}
start    = 0x00D8
size     = 6
mappings = {
    "O","U","U","U","U","Y"
}
start    = 0x00E0
size     = 23
mappings = {
    "a","a","a","a","a","a","ae","c",
    "e","e","e","e","i","i","i", "i",
    "d","n","o","o","o","o","o"
}
start    = 0x00F8
size     = 6
mappings = {
    "o","u","u","u","u","y"
}
: : :

使用スパース配列を使用すると、効率的にしても、それらのUnicodeテーブルの広い間隔のセクションの置換を表すことができます。文字列の置換により、任意のシーケンスで分音記号を置き換えることができます(æ書記素がになるなどae)。

これは言語にとらわれない回答なので、特定の言語を念頭に置いている場合は、より良い方法があります(とにかく、すべての言語が最低レベルでこれに到達する可能性があります)。


考えられるすべての奇妙な文字を追加するのは簡単なことではありません。数文字だけこれを行う場合、それは良い解決策です。
Simon Forsberg

2

考慮すべきこと:各単語の単一の「翻訳」を取得しようとするルートに行く場合、いくつかの可能な代替案を見逃す可能性があります。

たとえば、ドイツ語で「s-set」を置き換えるときに、「B」を使用する人もいれば「ss」を使用する人もいます。または、ウムラウトされたoを「o」または「oe」に置き換えます。あなたが思いつくどんな解決策も、理想的には、両方を含むべきだと思います。


2

Windowsと.NETでは、文字列エンコーディングを使用して変換するだけです。そうすれば、手動でのマッピングとコーディングを回避できます。

文字列エンコーディングで遊んでみてください。


3
文字列のエンコードについて詳しく説明できますか?たとえば、コード例を示します。
Peter Mortensen

2

ドイツ語の場合、ウムラウトから発音区別符号を削除することは望ましくありません(ä、ö、ü)。代わりに、それらは2文字の組み合わせ(ae、oe、ue)に置き換えられます。たとえば、正しい発音をするためには、BjörnをBjoern(Bjornではなく)と書く必要があります。

そのために、ハードコーディングされたマッピングが必要です。マッピングでは、特殊文字グループごとに個別に置換ルールを定義できます。


0

後で参照できるように、アクセントを削除するC#拡張メソッドを次に示します。

public static class StringExtensions
{
    public static string RemoveDiacritics(this string str)
    {
        return new string(
            str.Normalize(NormalizationForm.FormD)
                .Where(c => CharUnicodeInfo.GetUnicodeCategory(c) != 
                            UnicodeCategory.NonSpacingMark)
                .ToArray());
    }
}
static void Main()
{
    var input = "ŃŅŇ ÀÁÂÃÄÅ ŢŤţť Ĥĥ àáâãäå ńņň";
    var output = input.RemoveDiacritics();
    Debug.Assert(output == "NNN AAAAAA TTtt Hh aaaaaa nnn");
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.