如何比较“看起来相似”的Unicode字符?

时间:2013-12-19 06:11:49

标签: c# .net string unicode string-comparison

我陷入了一个令人惊讶的问题。

我在我的应用程序中加载了一个文本文件,我有一些逻辑比较了μ的值。

我意识到即使文本相同,比较值也是错误的。

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

在后面的行中,字符μ被复制粘贴。

但是,这些可能不是唯一的字符。

C#中是否有任何方法可以比较看起来相同但实际上不同的字符?

10 个答案:

答案 0 :(得分:149)

因为它们实际上是不同的符号,即使它们看起来相同,首先是实际的字母,并且有char code = 956 (0x3BC),第二个是微型符号,并且181 (0xB5)

参考文献:

因此,如果您想要比较它们并且需要它们相等,则需要手动处理它,或者在比较之前将一个char替换为另一个char。或者使用以下代码:

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

Demo

答案 1 :(得分:121)

在许多情况下,在比较它们之前,您可以normalize将两个Unicode字符转换为某个规范化形式,并且它们应该能够匹配。当然,您需要使用哪种规范化形式取决于角色本身;仅仅因为他们看起来并不一定意味着他们代表相同的角色。您还需要考虑它是否适合您的用例 - 请参阅Jukka K. Korpela的评论。

对于这种特殊情况,如果您参考Tony's answer中的链接,您会看到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
    }
}

有关Unicode规范化和不同规范化表单的详细信息,请参阅System.Text.NormalizationFormthe Unicode spec

答案 2 :(得分:86)

它们都有不同的字符代码:Refer this for more details

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

其中,第一个是:

Display     Friendly Code   Decimal Code    Hex Code    Description
====================================================================
μ           μ            μ          μ     Lowercase Mu
µ           µ         µ          µ      micro sign Mu

Image

答案 3 :(得分:38)

对于μ(mu)和µ(微信号)的具体示例,后者对前者有compatibility decomposition,因此您可以normalize字符串到FormKCFormKD将微观信号转换为mus。

但是,有许多字符集看起来很相似,但在任何Unicode规范化形式下都不相同。例如,A(拉丁语),Α(希腊语)和А(西里尔语)。 Unicode网站有一个confusables.txt文件及其列表,旨在帮助开发人员防范homograph attacks。如有必要,您可以解析此文件并为字符串的“可视化规范化”构建一个表。

答案 4 :(得分:34)

Search Unicode database中的两个字符,并看到差异

一个是Greek small Letter µ,另一个是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)

答案 5 :(得分:24)

编辑此问题与How to compare 'μ' and 'µ' in C#合并后 原帖回答:

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

修改 阅读评论后,是的,使用上述方法并不好,因为它可能会为其他类型的输入提供错误的结果,为此,我们应该使用normalize中提到的完全兼容性分解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的归一化,但在U + FB03的 NFC 归一化中没有归一化。

为了比较等价,我们通常应该使用 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:2081 Characters(int value): 55296-57343, 64976-65007, 65534

这个链接对于理解管理Unicode等价的规则非常有帮助

  1. Unicode_equivalence
  2. Unicode_compatibility_characters

答案 6 :(得分:9)

最有可能的是,有两个不同的字符代码可以(明显地)生成相同的字符。虽然技术上并不相同,但看起来并不相同。查看字符表并查看该字符是否有多个实例。或者在代码中打印出两个字符的字符代码。

答案 7 :(得分:6)

你问“如何比较它们”,但你没有告诉我们你想做什么。

至少有两种主要方法可以比较它们:

要么直接比较它们,要么它们是不同的

如果您需要进行比较以查找匹配项,则使用Unicode兼容性规范化。

可能存在问题,因为Unicode兼容性规范化会使许多其他字符比较相等。如果您只想将这两个字符视为相似,则应该使用自己的规范化或比较函数。

对于更具体的解决方案,我们需要了解您的具体问题。你遇到这个问题的背景是什么?

答案 8 :(得分:5)

如果我想迂腐,我会说你的问题没有意义,但由于我们正在接近圣诞节并且鸟儿正在唱歌,我会继续这样做。

首先,您要比较的2个实体是glyph s,字形是由通常称为“字体”提供的一组字形的一部分,通常是ttfotf或您正在使用的任何文件格式。

字形是给定符号的表示,并且由于它们是依赖于特定集合的表示,因此您不能期望具有2个相似或甚至“更好”的相同符号,这是一个不具有的短语如果你考虑上下文有意义,你至少应该在你提出这样的问题时指定你正在考虑的字体或字体集。

通常用于解决与您遇到的问题类似的问题,它是一个OCR,本质上是识别和比较字形的软件,如果C#默认提供OCR我不知道,但如果你真的不需要OCR并且你知道该如何处理它通常是一个非常糟糕的主意。

你最终可能会将一本物理书籍解读为一本古希腊书籍而不提及OCR在资源方面通常很昂贵的事实。

有一个原因可以解释为什么这些字符的本地化方式是本地化的,只是不这样做。

答案 9 :(得分:1)

可以使用DrawString方法绘制具有相同字体样式和大小的两个字符。生成两个带符号的位图后,可以逐个像素地比较它们。

这种方法的优点是你不仅可以比较绝对相等的字符,而且可以比较相似(具有明确的容差)。