program story

“유사하게 보이는”유니 코드 문자를 비교하는 방법은 무엇입니까?

inputbox 2020. 7. 29. 08:12
반응형

“유사하게 보이는”유니 코드 문자를 비교하는 방법은 무엇입니까?


나는 놀라운 문제에 빠진다.

응용 프로그램에 텍스트 파일을로드했으며 µ 값을 비교하는 논리가 있습니다.

그리고 나는 텍스트가 동일하더라도 비교 값이 거짓이라는 것을 깨달았습니다.

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

나중 줄에서 문자 µ가 복사 붙여 넣기됩니다.

그러나 이것 만이 이와 같은 문자는 아닙니다.

C #에서 똑같이 보이지만 실제로 다른 문자를 비교할 수있는 방법이 있습니까?


많은 경우에, 당신은 할 수 있습니다 정상화 를 비교하기 전에 특정 정상화 폼에 유니 코드 문자를 모두, 그들은 일치 할 수 있어야한다. 물론 어떤 정규화 형식을 사용해야하는지는 문자 자체에 따라 다릅니다. 단지 그들이 때문에 필요가 같은 문자를 나타내는 의미하지 않는다 모두. 사용 사례에 적합한 지 고려해야합니다. Jukka K. Korpela의 의견을 참조하십시오.

이 특정 상황에서 Tony의 답변 링크를 참조하면 U + 00B5 의 표에 다음과 같이 표시됩니다.

분해 <compat> GREEK SMALL LETTER MU (U + 03BC)

이는 원래 비교에서 두 번째 문자 인 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
    }
}

유니 코드 표준화와 다른 정규화 형식에 대한 자세한 내용을 참조 System.Text.NormalizationForm하고 유니 코드 사양 .


문자가 같더라도 실제로는 다른 기호이므로 첫 번째 문자는 실제 문자이고 code = 956 (0x3BC)두 번째 문자 는 마이크로 부호이며 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);
}

그리고 데모


둘 다 다른 문자 코드를 가지고 있습니다 : 자세한 내용은 이것을 참조하십시오

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

영상


μμ 및 µ마이크로 부호 의 특정 예의 경우, 후자는 전자와의 호환성 분해 를 가지므로 문자열을 정규화FormKC 하거나 FormKD마이크로 부호를 mus로 변환 할 수 있습니다 .

그러나 비슷하게 보이지만 유니 코드 정규화 형식에서는 동일하지 않은 문자 집합이 많이 있습니다. 예를 들어 A(라틴어), Α(그리스어) 및 А(키릴 자모). 유니 코드 웹 사이트에는 개발자가 호모 그래프 공격으로부터 보호하는 데 도움 이되는 confusables.txt 파일이 있습니다. 필요한 경우이 파일을 구문 분석하고 문자열의 "시각적 정규화"에 대한 테이블을 작성할 수 있습니다.


유니 코드 데이터베이스 에서 두 문자를 모두 검색 하여 차이점을 확인하십시오 .

하나는 그리스어 소문자 µ 이고 다른 하나는 마이크로 사인 µ 입니다.

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)

EDIT After the merge of this question with How to compare 'μ' and 'µ' in C#
Original answer posted:

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

EDIT After reading the comments, yes it is not good to use the above method because it may provide wrong results for some other type of inputs, for this we should use normalize using full compatibility decomposition as mentioned in wiki. (Thanks to the answer posted by 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();
    }

Output

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
 ________________________________________________________________

While reading information in Unicode_equivalence I found

The choice of equivalence criteria can affect search results. For instance some typographic ligatures like U+FB03 (ffi), ..... so a search for U+0066 (f) as substring would succeed in an NFKC normalization of U+FB03 but not in NFC normalization of U+FB03.

So to compare equivalence we should normally use FormKC i.e. NFKC normalization or FormKD i.e NFKD normalization.
I was little curious to know more about all the Unicode characters so I made sample which would iterate over all the Unicode character in UTF-16 and I got some results I want to discuss

  • Information about characters whose FormC and FormD normalized values were not equivalent
    Total: 12,118
    Character (int value): 192-197, 199-207, 209-214, 217-221, 224-253, ..... 44032-55203
  • Information about characters whose FormKC and FormKD normalized values were not equivalent
    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
  • All the character whose FormC and FormD normalized value were not equivalent, there FormKC and FormKD normalized values were also not equivalent except these characters
    Characters: 901 '΅', 8129 '῁', 8141 '῍', 8142 '῎', 8143 '῏', 8157 '῝', 8158 '῞'
    , 8159 '῟', 8173 '῭', 8174 '΅'
  • Extra character whose FormKC and FormKD normalized value were not equivalent, but there FormC and FormD normalized values were equivalent
    Total: 119
    Characters: 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'
  • There are some characters which can not be normalized, they throw ArgumentException if tried
    Total:2081 Characters(int value): 55296-57343, 64976-65007, 65534

This links can be really helpful to understand what rules govern for Unicode equivalence

  1. Unicode_equivalence
  2. Unicode_compatibility_characters

Most likely, there are two different character codes that make (visibly) the same character. While technically not equal, they look equal. Have a look at the character table and see whether there are multiple instances of that character. Or print out the character code of the two chars in your code.


You ask "how to compare them" but you don't tell us what you want to do.

There are at least two main ways to compare them:

Either you compare them directly as you are and they are different

Or you use Unicode Compatibility Normalization if your need is for a comparison that finds them to match.

There could be a problem though because Unicode compatibility normalization will make many other characters compare equal. If you want only these two characters to be treated as alike you should roll your own normalization or comparison functions.

For a more specific solution we need to know your specific problem. What is the context under which you came across this problem?


If I would like to be pedantic, I would say that your question doesn't make sense, but since we are approaching christmas and the birds are singing, I'll proceed with this.

First off, the 2 entities that you are trying to compare are glyphs, a glyph is part of a set of glyphs provided by what is usually know as a "font", the thing that usually comes in a ttf, otf or whatever file format you are using.

The glyphs are a representation of a given symbol, and since they are a representation that depends on a specific set, you can't just expect to have 2 similar or even "better" identical symbols, it's a phrase that doesn't make sense if you consider the context, you should at least specify what font or set of glyphs you are considering when you formulate a question like this.

What is usually used to solve a problem similar to the one that you are encountering, it's an OCR, essentially a software that recognize and compares glyphs, If C# provides an OCR by default I don't know that, but it's generally a really bad idea if you don't really need an OCR and you know what to do with it.

OCR이 일반적으로 자원면에서 비싸다는 사실을 언급하지 않고 물리 책을 고대 그리스 책으로 해석하게 될 수 있습니다.

해당 문자가 현지화 된 방식으로 현지화 된 이유는 있지만 그렇게하지 마십시오.


메소드를 사용하여 동일한 글꼴 스타일과 크기로 두 문자를 모두 그릴 수 있습니다 DrawString. 심볼이있는 두 개의 비트 맵이 생성 된 후에는 픽셀 단위로 비교할 수 있습니다.

이 방법의 장점은 절대적으로 동일한 문자를 비교할 수있을뿐만 아니라 (공차가 분명한) 비슷한 것도 있습니다.

참고 URL : https://stackoverflow.com/questions/20674300/how-to-compare-%ce%bc-and-%c2%b5-in-c-sharp

반응형