web-dev-qa-db-ja.com

「似ている」ユニコード文字を比較する方法は?

私は驚くべき問題に陥ります。

アプリケーションにテキストファイルを読み込んだところ、µを持つ値を比較するロジックがいくつかありました。

そして、テキストが同じであっても、比較値が偽であることを認識しました。

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

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

ただし、このようなキャラクターはこれらだけではありません。

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

94
D J

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

この特定の状況について、 Tony's answer のリンクを参照すると、 + 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 および nicode仕様

124
BoltClock

同じように見えても実際には異なるシンボルであるため、最初は実際の文字でchar code = 956 (0x3BC)を持ち、2番目はマイクロ記号で181 (0xB5)を持ちます。

参照:

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

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

そして デモ

149
Tony

両方とも異なる文字コードを持っています: 詳細についてはこれを参照してください

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

ここで、最初のものは:

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

Image

86
Vishal Suthar

μ(mu)およびµ(マイクロ記号)の特定の例では、後者には前者との 互換性分解 があるため、 正規化 文字列をFormKCまたはFormKDに変換して、マイクロ記号をmusに変換します。

ただし、似ているがUnicode正規化形式では同等ではない文字のセットがたくさんあります。たとえば、A(ラテン)、Α(ギリシャ語)、およびА(キリル文字)。 Unicode Webサイトには、開発者が homograph attack から保護できるようにすることを目的とした、これらのリストを含む confusables.txt ファイルがあります。必要に応じて、このファイルを解析し、文字列の「視覚的正規化」用のテーブルを作成できます。

38
dan04

検索 の両方の文字 nicodeデータベース を参照して、differenceを参照してください。

1つは ギリシャ語の小文字µともう1つは Micro Signµ

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)
34
Subin Jacob

[〜#〜] edit [〜#〜]この質問と のマージ後C#で「μ」と「µ」を比較する方法
投稿されたオリジナルの回答:

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

[〜#〜] edit [〜#〜]コメントを読んだ後、上記の方法を使用するのは良くありません。他のタイプの入力に対して間違った結果を提供する可能性があるためです。 wiki で説明されているように、完全な互換性分解を使用して normalize を使用します。 ( 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)のsearchsucceedで- [〜#〜] nfkc [〜#〜] U + FB03の正規化ですが、[〜#〜] nfc [〜#〜] U + FB03の正規化ではありません。

したがって、同等性を比較するには、通常FormKCすなわちNFKC正規化またはFormKDすなわちNFKD正規化を使用する必要があります。
すべてのUnicode文字について詳しく知りたいとは思わなかったので、UTF-16のすべてのUnicode文字を反復処理するサンプルを作成し、議論したい結果が得られました。

  • FormCFormDの正規化された値が等しくない文字に関する情報
    Total: 12,118
    Character (int value): 192-197, 199-207, 209-214, 217-221, 224-253, ..... 44032-55203
  • FormKCFormKDの正規化された値が等しくない文字に関する情報
    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:2081Characters(int value): 55296-57343, 64976-65007, 65534

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

  1. Unicode_equivalence
  2. Unicode_compatibility_characters
24
dbw

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

9
PMF

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

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

そのままの形で直接比較しますが、違います

または、一致するものを見つける比較が必要な場合は、Unicode Compatibility Normalizationを使用します。

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

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

6
hippietrail

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

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

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

C#が [〜#〜] ocr [ 〜#〜] デフォルトではそれを知りませんが、OCRを本当に必要とせず、それをどう処理するかを知っている場合、一般的には非常に悪い考えです。

OCRが一般的にリソースの点で高価であるという事実に言及することなく、物理学の本を古代ギリシャの本として解釈することになる可能性があります。

これらの文字がローカライズされた方法でローカライズされる理由がありますが、そうしないでください。

5
user2485710

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

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

1
Ivan Kochurkin