「似ている」Unicode文字を比較する方法は?


94

私は意外な問題に陥ります。

アプリケーションにテキストファイルをロードし、µの値を比較するロジックがあります。

そして、テキストが同じでも比較値がfalseであることに気付きました。

 Console.WriteLine("μ".Equals("µ")); // returns false
 Console.WriteLine("µ".Equals("µ")); // return true

後の行で、文字µがコピーペーストされます。

ただし、このような文字はこれらだけではない可能性があります。

同じように見えても実際には異なる文字をC#で比較する方法はありますか?


158
シュレーディンガーのミューを見つけたようです。
BoltClock

19
それらは異なる文字です-それらは同じに見えても、異なる文字コードを持っています。
user2864740 2013

93
Unicodeへようこそ。
ta.speot.is 2013

11
何を達成したいですか?これらの2つは等しいはずであり、文字コードも異なるが同じ顔である必要がありますか
ジェイド、

28
「似ている」と「同じに見える」はあいまいな概念です。それらはグリフの同一性を意味するのか、それとも単に類似性を意味するのか どれだけ近いか?2つの文字は、一部のフォントでは同一のグリフを持ち、別のフォントでは非常に類似していて、さらに別のフォントではまったく異なる場合があることに注意してください。重要なのは、なぜそのような比較をどのような状況で行うの(そして、誤検知と誤検知の許容性)です。
Jukka K. Korpela

回答:


125

多くの場合、あなたがすることができます正常化、それらを比較する前に、特定の正規化形式にUnicode文字の両方を、彼らは一致することができるはずです。もちろん、どの正規化形式を使用する必要があるかは、文字自体によって異なります。彼らはちょうどので、見て似必ずしもそれらが同じ文字を表すという意味ではありません。また、ユースケースに適しているかどうかを検討する必要があります。JukkaK. Korpelaのコメントを参照してください。

この特定の状況で、Tonyの回答のリンクを参照すると、U + 00B5の表に次のように記載されていることがわかります。

分解<compat>ギリシャ語小文字MU(U + 03BC)

これは、元の比較の2番目の文字であるU + 00B5を最初の文字であるU + 03BCに分解できることを意味します。

したがって、完全な互換性分解を使用して、正規化形式KCまたはKDで文字を正規化します。これは私がデモンストレーションするために書いた簡単な例です:

using System;
using System.Text;

class Program
{
    static void Main(string[] args)
    {
        char first = 'μ';
        char second = 'µ';

        // Technically you only need to normalize U+00B5 to obtain U+03BC, but
        // if you're unsure which character is which, you can safely normalize both
        string firstNormalized = first.ToString().Normalize(NormalizationForm.FormKD);
        string secondNormalized = second.ToString().Normalize(NormalizationForm.FormKD);

        Console.WriteLine(first.Equals(second));                     // False
        Console.WriteLine(firstNormalized.Equals(secondNormalized)); // True
    }
}

Unicodeの正規化とさまざまな正規化形式の詳細については、System.Text.NormalizationFormおよびUnicode仕様を参照してください。


26
Unicode仕様のリンクをありがとう。初めて読んだ時。それからの小さなメモ:「正規化フォームKCおよびKDは、任意のテキストに盲目的に適用してはなりません。これらの正規化フォームは大文字または小文字のマッピングのようであると考えることが最善です。常に適切であるとは限らないテキストの修正。」
user2864740 2013

149

それらは同じに見えても実際には異なる記号であるため、最初は実際の文字でcharがcode = 956 (0x3BC)あり、2番目はマイクロ記号でがあり181 (0xB5)ます。

参照:

したがって、それらを比較する必要があり、それらを等しくする必要がある場合は、手動で処理するか、比較前に1つの文字を別の文字に置き換える必要があります。または、次のコードを使用します。

public void Main()
{
    var s1 = "μ";
    var s2 = "µ";

    Console.WriteLine(s1.Equals(s2));  // false
    Console.WriteLine(RemoveDiacritics(s1).Equals(RemoveDiacritics(s2))); // true 
}

static string RemoveDiacritics(string text) 
{
    var normalizedString = text.Normalize(NormalizationForm.FormKC);
    var stringBuilder = new StringBuilder();

    foreach (var c in normalizedString)
    {
        var unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(c);
        if (unicodeCategory != UnicodeCategory.NonSpacingMark)
        {
            stringBuilder.Append(c);
        }
    }

    return stringBuilder.ToString().Normalize(NormalizationForm.FormC);
}

そしてデモ


11
好奇心から、µシンボルを2つ持つ理由は何ですか?「Kilo sign」という名前の専用のKは表示されません(またはそうですか)。
MartinHaTh 2013

12
@MartinHaTh:ウィキペディアによると、これは「歴史的な理由」によるものです。
BoltClock

12
Unicodeには、古い文字セット(ISO 8859-1など)から引き継がれた多くの互換文字があり、これらの文字セットからの変換が容易になります。文字セットが8ビットに制限されていたとき、最も一般的な数学と科学の用途のために、いくつかのグリフ(ギリシャ文字など)が含まれていました。外観に基づくグリフの再利用が一般的だったため、特別な「K」は追加されませんでした。しかし、それは常に回避策でした。「micro」の正しい記号は実際のギリシャ語の小文字のmu、Ohmの正しい記号は実際の大文字のオメガなどです。
VGR

8
ヒステリックなレーズンのために何かが行われたときより良いものは何もありません
paulm

11
シリアル用の特別なKはありますか?

86

どちらも文字コードが異なります。詳しくはこちらをご覧ください

Console.WriteLine((int)'μ');  //956
Console.WriteLine((int)'µ');  //181

ここで、1つ目は次のとおりです。

Display     Friendly Code   Decimal Code    Hex Code    Description
====================================================================
μ           &mu;            &#956;          &#x3BC;     Lowercase Mu
µ           &micro;         &#181;          &#xB5;      micro sign Mu

画像


39

μ(mu)とµ(micro sign)の特定の例では、後者は前者と互換性があります。そのため、文字列を正規化FormKCたり、micro sign FormKDをmusに変換したりできます。

ただし、Unicodeの正規化形式では、同じように見えても同等ではない文字のセットが多数あります。たとえば、A(ラテン語)、Α(ギリシャ語)、А(キリル文字)などです。Unicodeウェブサイトには、開発者がホモグラフ攻撃から保護することを目的としたこれらのリストを含むconfusables.txtファイルがあります。必要に応じて、このファイルを解析し、文字列の「視覚的な正規化」のためのテーブルを作成できます。


ノーマライズを使用するときに知っておくと間違いありません。それらが別個のままであることは驚くべきことです。
user2864740 2013

4
@ user2864740:ギリシャ語の大文字のタウがローマ字のTと区別されない場合、ギリシャ語とローマ字のテキストをアルファベット順に適切にソートすることは非常に困難です。さらに、書体がギリシャ文字とローマ文字で異なる視覚スタイルを使用する場合、形状がローマ文字に似ているギリシャ文字が、そうでないものとは異なってレンダリングされると、非常に煩わしくなります。
スーパーキャット2013

7
さらに重要なのは、ヨーロッパのアルファベットを統一することになるだろうToUpper/ ToLower実装することは困難。あなたは英語である必要があり"B".ToLower()ますbβ、ギリシャ語とвロシア語である必要があります。現状では、トルコ語(ドットなしi)と他のいくつかの言語のみがデフォルトと異なる大文字小文字の規則を必要とします。
dan04 2013

@ dan04:トルコの "i"と "I"の4つのバリエーションすべてに一意のコードポイントを割り当てることを検討した人はいるのではないでしょうか。これにより、toUpper / toLowerの動作のあいまいさが解消されます。
スーパーキャット2014

34

Unicodeデータベースで両方の文字を検索し違いを確認します

1つはギリシャ語の小文字 µ、もう1つはマイクロサイン µです。

Name            : MICRO SIGN
Block           : Latin-1 Supplement
Category        : Letter, Lowercase [Ll]
Combine         : 0
BIDI            : Left-to-Right [L]
Decomposition   : <compat> GREEK SMALL LETTER MU (U+03BC)
Mirror          : N
Index entries   : MICRO SIGN
Upper case      : U+039C
Title case      : U+039C
Version         : Unicode 1.1.0 (June, 1993)

Name            : GREEK SMALL LETTER MU
Block           : Greek and Coptic
Category        : Letter, Lowercase [Ll]
Combine         : 0
BIDI            : Left-to-Right [L]
Mirror          : N
Upper case      : U+039C
Title case      : U+039C
See Also        : micro sign U+00B5
Version         : Unicode 1.1.0 (June, 1993)

4
これはどのように37票を獲得しましたか?質問(「Unicode文字の比較方法」)には答えません。この特定の例が等しくない理由についてコメントするだけです。せいぜい、それは質問に対するコメントであるべきです。コメントの書式設定オプションでは、回答の書式設定オプションほどうまく投稿できないことを理解していますが、それが回答として投稿する正当な理由ではないはずです。
Konerak 2013

5
実際には、μとµの等式チェックがfalseを返す理由を尋ねる別の質問でした。この答えが答えます。後にOPは別の質問(この質問)に、似ている2つの文字を比較する方法を尋ねました。両方の質問に最良の回答があり、後でモデレーターの1人が両方の質問をマージして、2番目の質問の最良の回答を最良として選択しました。要約するように誰かがこの質問を編集しました
Subin Jacob

実際、私はマージ後にコンテンツを追加しませんでした
Subin Jacob '31

24

編集この質問とC#の「μ」と「μ」を比較する方法とのマージ後、
元の回答が投稿されました:

 "μ".ToUpper().Equals("µ".ToUpper()); //This always return true.

編集 コメントを読んだ後、はい、上記の方法を使用することは他のタイプの入力に対して誤った結果を提供する可能性があるため、適切ではありません。これについては、wikiで言及されている完全互換分解を使用して正規化する必要があります。(BoltClockによって投稿された回答のおかげで)

    static string GREEK_SMALL_LETTER_MU = new String(new char[] { '\u03BC' });
    static string MICRO_SIGN = new String(new char[] { '\u00B5' });

    public static void Main()
    {
        string Mus = "µμ";
        string NormalizedString = null;
        int i = 0;
        do
        {
            string OriginalUnicodeString = Mus[i].ToString();
            if (OriginalUnicodeString.Equals(GREEK_SMALL_LETTER_MU))
                Console.WriteLine(" INFORMATIO ABOUT GREEK_SMALL_LETTER_MU");
            else if (OriginalUnicodeString.Equals(MICRO_SIGN))
                Console.WriteLine(" INFORMATIO ABOUT MICRO_SIGN");

            Console.WriteLine();
            ShowHexaDecimal(OriginalUnicodeString);                
            Console.WriteLine("Unicode character category " + CharUnicodeInfo.GetUnicodeCategory(Mus[i]));

            NormalizedString = OriginalUnicodeString.Normalize(NormalizationForm.FormC);
            Console.Write("Form C Normalized: ");
            ShowHexaDecimal(NormalizedString);               

            NormalizedString = OriginalUnicodeString.Normalize(NormalizationForm.FormD);
            Console.Write("Form D Normalized: ");
            ShowHexaDecimal(NormalizedString);               

            NormalizedString = OriginalUnicodeString.Normalize(NormalizationForm.FormKC);
            Console.Write("Form KC Normalized: ");
            ShowHexaDecimal(NormalizedString);                

            NormalizedString = OriginalUnicodeString.Normalize(NormalizationForm.FormKD);
            Console.Write("Form KD Normalized: ");
            ShowHexaDecimal(NormalizedString);                
            Console.WriteLine("_______________________________________________________________");
            i++;
        } while (i < 2);
        Console.ReadLine();
    }

    private static void ShowHexaDecimal(string UnicodeString)
    {
        Console.Write("Hexa-Decimal Characters of " + UnicodeString + "  are ");
        foreach (short x in UnicodeString.ToCharArray())
        {
            Console.Write("{0:X4} ", x);
        }
        Console.WriteLine();
    }

出力

INFORMATIO ABOUT MICRO_SIGN    
Hexa-Decimal Characters of µ  are 00B5
Unicode character category LowercaseLetter
Form C Normalized: Hexa-Decimal Characters of µ  are 00B5
Form D Normalized: Hexa-Decimal Characters of µ  are 00B5
Form KC Normalized: Hexa-Decimal Characters of µ  are 03BC
Form KD Normalized: Hexa-Decimal Characters of µ  are 03BC
 ________________________________________________________________
 INFORMATIO ABOUT GREEK_SMALL_LETTER_MU    
Hexa-Decimal Characters of µ  are 03BC
Unicode character category LowercaseLetter
Form C Normalized: Hexa-Decimal Characters of µ  are 03BC
Form D Normalized: Hexa-Decimal Characters of µ  are 03BC
Form KC Normalized: Hexa-Decimal Characters of µ  are 03BC
Form KD Normalized: Hexa-Decimal Characters of µ  are 03BC
 ________________________________________________________________

Unicode_equivalenceで情報を読んでいるときに見つけた

等価基準の選択は、検索結果に影響を与える可能性があります。例えばU + FB03(FFI)のようないくつかの活版印刷の合字は、.....ので、検索ストリングとしてU + 0066(F)については、考え、成功NFKCの U + FB03の正常化ではなく、中のNFC U + FB03の正常化。

したがって、同等性を比較するには、通常FormKCNFKC正規化またはNFKD正規化を使用する必要がありますFormKD
すべてのUnicode文字について詳しく知りUTF-16たくなかったので、すべてのUnicode文字を繰り返し処理するサンプルを作成し、検討したい結果を得ました。

  • FormCおよびFormD正規化された値が同等ではない文字に関する情報
    Total: 12,118
    Character (int value): 192-197, 199-207, 209-214, 217-221, 224-253, ..... 44032-55203
  • FormKCおよびFormKD正規化された値が同等ではない文字に関する情報
    Total: 12,245
    Character (int value): 192-197, 199-207, 209-214, 217-221, 224-228, ..... 44032-55203, 64420-64421, 64432-64433, 64490-64507, 64512-64516, 64612-64617, 64663-64667, 64735-64736, 65153-65164, 65269-65274
  • すべての文字を持つFormCFormD正規化した値は等価ではありませんでした、そこFormKCFormKD正規化された値は、これらの文字を除いて同じではありませんでした
    文字:901 '΅', 8129 '῁', 8141 '῍', 8142 '῎', 8143 '῏', 8157 '῝', 8158 '῞'
    , 8159 '῟', 8173 '῭', 8174 '΅'
  • 余分な文字を持つFormKCFormKD正規化した値は同等ではありませんでしたが、そこFormCFormD正規化された値は同等であった
    Total: 119
    文字:452 'DŽ' 453 'Dž' 454 'dž' 12814 '㈎' 12815 '㈏' 12816 '㈐' 12817 '㈑' 12818 '㈒' 12819 '㈓' 12820 '㈔' 12821 '㈕', 12822 '㈖' 12823 '㈗' 12824 '㈘' 12825 '㈙' 12826 '㈚' 12827 '㈛' 12828 '㈜' 12829 '㈝' 12830 '㈞' 12910 '㉮' 12911 '㉯' 12912 '㉰' 12913 '㉱' 12914 '㉲' 12915 '㉳' 12916 '㉴' 12917 '㉵' 12918 '㉶' 12919 '㉷' 12920 '㉸' 12921 '㉹' 12922 '㉺' 12923 '㉻' 12924 '㉼' 12925 '㉽' 12926 '㉾' 13056 '㌀' 13058 '㌂' 13060 '㌄' 13063 '㌇' 13070 '㌎' 13071 '㌏' 13072 '㌐' 13073 '㌑' 13075 '㌓' 13077 '㌕' 13080 '㌘' 13081 '㌙' 13082 '㌚' 13086 '㌞' 13089 '㌡' 13092 '㌤' 13093 '㌥' 13094 '㌦' 13099 '㌫' 13100 '㌬' 13101 '㌭' 13102 '㌮' 13103 '㌯' 13104 '㌰' 13105 '㌱' 13106 '㌲' 13108 '㌴' 13111 '㌷' 13112 '㌸' 13114 '㌺' 13115 '㌻' 13116 '㌼' 13117 '㌽' 13118 '㌾' 13120 '㍀' 13130 '㍊' 13131 '㍋' 13132 '㍌' 13134 '㍎' 13139 '㍓' 13140 '㍔' 13142 '㍖' .......... ﺋ' 65164 'ﺌ' 65269 'ﻵ' 65270 'ﻶ' 65271 'ﻷ' 65272 'ﻸ' 65273 'ﻹ' 65274'
  • 正規化できない文字がありますがArgumentException、試してみると投げます
    Total:2081 Characters(int value): 55296-57343, 64976-65007, 65534

このリンクは、Unicodeの同等性に適用されるルールを理解するのに非常に役立ちます。

  1. Unicode_equivalence
  2. Unicode_compatibility_characters

4
奇妙ですが動作します...つまり、意味が異なる2つの異なる文字であり、それらをアッパーに変換すると、それらは等しくなりますか?ロジックはわかりませんが、すばらしい解決策+1
BudBrot

45
このソリューションは問題を隠蔽し、一般的なケースで問題を引き起こす可能性があります。テストのこの種はそれを見つけるだろう"m".ToUpper().Equals("µ".ToUpper());し、"M".ToUpper().Equals("µ".ToUpper());また真です。これは望ましくない場合があります。
Andrew Leach、

6
-1 –これはひどい考えです。このようなUnicodeを使用しないでください。
Konrad Rudolph

1
ToUpper()ベースのトリックの代わりに、String.Equals( "μ"、 "μ"、StringComparison.CurrentCultureIgnoreCase)を使用しないのはなぜですか?
svenv 2013

6
「MICRO SIGN」と「GREEK SMALL LETTER MU」を区別する1つの理由があります-マイクロ記号の「大文字」は依然としてマイクロ記号であると言うことです。しかし、大文字化によってマイクロからメガへと変化し、エンジニアリングがハッピーになります。
グレッグ

9

ほとんどの場合、同じ文字を(目に見える)にする2つの異なる文字コードがあります。技術的には等しくありませんが、見た目は同じです。キャラクターテーブルを見て、そのキャラクターの複数のインスタンスがあるかどうかを確認してください。または、コード内の2つの文字の文字コードを出力します。


6

あなたは「それらを比較する方法」を尋ねますが、あなたが何をしたいのかを私たちに教えません。

それらを比較するには、少なくとも2つの主な方法があります。

そのまま直接比較するか、異なるか

または、一致するものを見つける比較が必要な場合は、Unicode互換性正規化を使用します。

ただし、Unicode互換性の正規化により、他の多くの文字が同等に比較されるため、問題が発生する可能性があります。これら2つの文字だけを同じように扱う場合は、独自の正規化関数または比較関数をロールする必要があります。

より具体的なソリューションについては、特定の問題を知る必要があります。この問題に遭遇した背景は何ですか?


1
「マイクロ記号」と小文字のmu文字は正規的に同等ですか?正規化正規化を使用すると、より厳密な比較ができます。
Tanner Swett、

@ TannerL.Swett:実際、私は頭の上からそれを確認する方法すらわかりません...
ヒッピートレイル

1
実は、物理計算式のファイルをインポートしていました。あなたは正規化について正しいです。私は...より深く、それを通過する必要があります
DJ

どのようなファイルですか?人がプレーンなUnicodeテキストで手作りしたものですか?または、特定の形式でアプリによって出力されたものですか?
ヒッピートレイル2013

5

知識を深めたいのであれば、あなたの質問は意味をなさないと言いますが、私たちはクリスマスに近づいており、鳥が歌っているので、これを続行します。

まず、比較しようとしている2つのエンティティはglyphsです。グリフは、通常「フォント」と呼ばれるものによって提供される一連のグリフの一部であり、通常はに含まれるものttfotfまたは任意のファイル形式です使用しています。

グリフは特定のシンボルの表現であり、特定のセットに依存する表現であるため、2つの類似した、または「より良い」同一のシンボルを期待することはできません。意味のないフレーズです。コンテキストを考慮する場合は、このような質問を作成するときに、少なくともどのフォントまたはグリフのセットを検討するかを指定する必要があります。

あなたが遭遇している問題と同様の問題を解決するために通常使用されるものはOCRであり、本質的にはグリフを認識して比較するソフトウェアです。C#がデフォルトでOCRを提供する場合、私はそれを知りませんが、それは一般的に本当に悪いですあなたが本当にOCRを必要とせず、あなたがそれをどうするか知っているなら、アイデア。

物理学の本を古代ギリシャ語の本として解釈することになるかもしれませんが、OCRは一般にリソースの点で高価であるという事実に言及していません。

これらの文字がローカライズされた方法でローカライズされるのには理由があります。それを行わないでください。


1

DrawStringメソッドで同じフォントスタイルとサイズの両方の文字を描画することが可能です。シンボルを含む2つのビットマップが生成された後、それらをピクセルごとに比較することができます。

この方法の利点は、絶対的に等しい文字だけでなく、同様に(明確な許容範囲で)比較できることです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.