私は発音区別記号付きの文字間でマッピングできるアルゴリズムを見ています( tilde 、 circumflex 、 caret 、 mlaut 、 caron )およびその「単純な」文字。
例えば:
ń ǹ ň ñ ṅ ņ ṇ ṋ ṉ ̈ ɲ ƞ ᶇ ɳ ȵ --> n
á --> a
ä --> a
ấ --> a
ṏ --> o
等。
Javaでこれを行いたいのですが、Unicode-yである必要があり、どの言語でも合理的に簡単に実行できるはずです。
目的:発音区別符号付きの単語を簡単に検索できるようにします。たとえば、テニスプレーヤーのデータベースがあり、Björn_Borgが入力されている場合は、Bjorn_Borgも保持するため、誰かがBjörnではなくBjornを入力した場合にそれを見つけることができます。
私は最近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;
}
}
コアJava.textパッケージは、この使用例(発音区別記号、大文字小文字などを気にせずに文字列を一致させる)に対処するように設計されました。
Collator
を構成して、 PRIMARY
文字の違いでソートします。それで、各文字列に対して CollationKey
を作成します。すべてのコードがJavaである場合、CollationKey
を直接使用できます。データベースまたはその他の種類のインデックスにキーを保存する必要がある場合は、 バイト配列に変換 を使用できます。
これらのクラスは、 nicode標準 大文字と小文字を区別するデータを使用して、どの文字が同等かを判断し、さまざまな 分解 戦略をサポートします。
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
クラスを使用すると、これらのルールをすべて追跡して最新の状態に保つ必要がなくなります。
Java.text
の Normalizerクラス を使用できます。
System.out.println(new String(Normalizer.normalize("ń ǹ ň ñ ṅ ņ ṇ ṋ", Normalizer.Form.NFKD).getBytes("ascii"), "ascii"));
ただし、Javaは変換不可能なUnicode文字で奇妙なことをします(それらを無視せず、例外をスローしません)。出発点として。
これらのマークのすべてが一部の「通常の」文字の「マーク」ではないことに注意してください。意味を変更せずに削除できます。
スウェーデン語では、åäとöは真で適切なファーストクラスの文字であり、他の文字の「バリアント」ではありません。それらは他のすべてのキャラクターとは異なって聞こえ、ソートも異なり、単語の意味を変えます(「mätt」と「matt」は2つの異なる単語です)。
Windowsと.NETでは、文字列エンコードを使用して変換します。そうすれば、手動でのマッピングとコーディングを回避できます。
文字列エンコーディングで遊んでみてください。
ドイツ語の場合、ウムラウトから発音区別符号を削除することは望ましくありません(ä、ö、ü)。代わりに、2つの文字の組み合わせ(ae、oe、ue)に置き換えられます。たとえば、BjörnはBjorn(Bjornではなく)と記述して正しい発音をする必要があります。
そのために、ハードコーディングされたマッピングがあり、特殊文字グループごとに個別に置換ルールを定義できます。
考慮すべきこと:各Wordの単一の「翻訳」を取得しようとするルートに進むと、いくつかの可能な代替案を見逃す可能性があります。
たとえば、ドイツ語で「s-set」を置き換える場合、「B」を使用する人もいれば「ss」を使用する人もいます。または、ウムラウトされたoを「o」または「oe」に置き換えます。理想的な解決策は、両方を含めるべきだと思います。
Unicodeには特定のディアリック文字(複合文字)があり、文字とディアトリックスが分離されるように文字列を変換できます。その後、文字列から発音記号を削除するだけで、基本的には完了です。
正規化、分解、および同等化の詳細については、 nicodeホームページ のThe Unicode Standardを参照してください。
ただし、実際にこれを達成する方法は、作業しているフレームワーク/ OS/...によって異なります。 .NETを使用している場合は、 System.Text.NormalizationForm 列挙型を受け入れる String.Normalize メソッドを使用できます。
(私にとって)最も簡単な方法は、単に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"
}
: : :
sparse配列を使用すると、Unicodeテーブルの間隔の広いセクションにある置換を効率的に表すことができます。文字列の置換により、任意のシーケンスで発音区別符号を置き換えることができます(æ
書記素はae
になります。
これは言語に依存しない回答なので、特定の言語を念頭に置いている場合は、より良い方法があります(とにかく、それらはすべて最低レベルでこれに到達するでしょう)。
将来の参照用に、アクセントを削除する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");
}