89

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

例えば:

ń  ǹ  ň  ñ  ṅ  ņ  ṇ  ṋ  ṉ  ̈  ɲ  ƞ ᶇ ɳ ȵ  --> n
á --> a
ä --> a
ấ --> a
ṏ --> o

等。

  1. 私はこれを Java で行いたいと思っていますが、それは Unicode のようなもので、どの言語でもかなり簡単に実行できるはずです。

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

4

12 に答える 12

86

私は最近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) {
        StringBuilder ret = new StringBuilder
        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;
    }
}
于 2009-09-21T07:43:40.420 に答える
25

コアの java.text パッケージは、このユース ケース (分音記号や大文字と小文字を気にせずに文字列を照合する) に対応するように設計されています。

文字の違いでCollatorソートするように を構成します。PRIMARYそれで、CollationKeyfor each 文字列を作成します。すべてのコードが 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クラスにより、これらすべてのルールを追跡して最新の状態に保つ必要がなくなります。

于 2009-09-21T14:32:03.977 に答える
17

バージョンのApache Commons Langの一部です。3.1.

org.apache.commons.lang3.StringUtils.stripAccents("Añ");

戻り値An

于 2012-10-14T10:22:13.343 に答える
12

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

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

しかし、Java は変換できない Unicode 文字を使用すると奇妙な処理を行うため、まだいくつかの作業が必要です (Java はそれらを無視せず、例外もスローしません)。しかし、それを出発点として使用できると思います。

于 2009-09-21T07:31:38.307 に答える
10

文字の折り畳みに関するドラフト レポートがUnicode Web サイトにあり、関連資料が多数あります。具体的にはセクション 4.1 を参照してください。「折りたたみアルゴリズム」。

ここでは、Perl を使用した分音記号の除去の説明と実装について説明します。

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

于 2009-09-21T07:13:14.507 に答える
5

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

スウェーデン語では、å ä と ö は真の適切な第一級文字であり、他の文字の「変形」ではありません。それらは他のすべての文字とは異なって聞こえ、並べ替えが異なり、単語の意味を変えます (「mätt」と「matt」は 2 つの異なる単語です)。

于 2010-03-01T15:46:15.453 に答える
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)。

これは言語に依存しない答えなので、特定の言語を念頭に置いている場合は、より良い方法があります(ただし、いずれにせよ、それらはすべて最低レベルでこれに到達する可能性があります)。

于 2009-09-21T07:41:05.110 に答える
2

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

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

于 2009-09-21T14:41:36.567 に答える
2

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

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

于 2013-02-08T10:16:12.773 に答える
2

Unicode には特定のダイアトリック文字 (複合文字) があり、文字とダイアトリックが分離されるように文字列を変換できます。次に、文字列からダイアトリクトを削除するだけで、基本的に完了です。

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

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

于 2009-09-21T07:10:10.383 に答える
2

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

たとえば、ドイツ語で「s-set」を置き換える場合、「B」を使用する人もいれば、「ss」を使用する人もいます。または、ウムラウト付きの o を「o」または「oe」に置き換えます。あなたが思いついた解決策は、理想的には、両方を含める必要があると思います。

于 2009-09-21T14:58:09.313 に答える
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");
}
于 2009-09-26T17:06:42.270 に答える