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

D J*_*D J 94 .net c# string unicode string-comparison

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

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

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

 Console.WriteLine("?".Equals("µ")); // returns false
 Console.WriteLine("µ".Equals("µ")); // return true
Run Code Online (Sandbox Code Playgroud)

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

但是,这些可能不是唯一像这样的字符.

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

Ton*_*ony 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);
}
Run Code Online (Sandbox Code Playgroud)

演示

  • @MartinHaTh:根据维基百科,它是["出于历史原因"](http://en.wikipedia.org/wiki/Micro-#Symbol_encoding_in_character_sets). (12认同)
  • Unicode具有许多从旧字符集(如[ISO 8859-1](http://en.wikipedia.org/wiki/ISO_8859-1))带来的兼容性字符,以便更容易地从这些字符集进行转换.当字符集被限制为8位时,它们将包括一些字形(如一些希腊字母),用于最常见的数学和科学用途.基于外观的字形重用是常见的,因此没有添加专门的"K".但它始终是一种解决方法; "micro"的正确符号是实际的希腊小写mu,Ohm的正确符号是实际的首都omega,依此类推. (12认同)
  • 出于好奇,有两个μ符号的原因是什么?你没有看到一个名为"Kilo sign"的专用K(或者你呢?). (11认同)
  • 谷物有特殊的K吗? (11认同)
  • 没有比为歇斯底里的葡萄干做些什么更好的了 (8认同)
  • *微优化*的特例. (3认同)

Bol*_*ock 124

在很多情况下,你可以正常化比较之前两个Unicode字符的一定范式,他们应该能够匹配.当然,您需要使用哪种规范化形式取决于角色本身; 仅仅因为它们看起来相似并不一定意味着它们代表相同的角色.您还需要考虑它是否适合您的用例 - 请参阅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
    }
}
Run Code Online (Sandbox Code Playgroud)

有关Unicode正常化细节和不同的标准化形式是指System.Text.NormalizationFormUnicode的规范.

  • 感谢Unicode规范链接.我第一次读它.从它的小注释:"规范化形式KC和KD不能盲目地应用于任意文本..最好将这些规范化形式视为大写或小写映射:在某些情况下用于识别核心含义,但也执行对文本的修改可能并不总是合适的." (26认同)

Vis*_*har 86

它们都有不同的字符代码:有关详细信息,请参阅此处

Console.WriteLine((int)'?');  //956
Console.WriteLine((int)'µ');  //181
Run Code Online (Sandbox Code Playgroud)

在哪里,第一个是:

Display     Friendly Code   Decimal Code    Hex Code    Description
====================================================================
?           &mu;            &#956;          &#x3BC;     Lowercase Mu
µ           &micro;         &#181;          &#xB5;      micro sign Mu
Run Code Online (Sandbox Code Playgroud)

图片


dan*_*n04 39

对于?(mu)和µ(微信号)的具体示例,后者对前者具有兼容性分解,因此您可以将字符串规范化FormKCFormKD将微观符号转换为mus.

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

  • 更重要的是,统一欧洲字母表会使"ToUpper"/"ToLower"难以实现.你需要有"B".ToLower()`在英语中是`b`但在希腊语中是`β`在俄语中是```.事实上,只有土耳其语(无网点`i`)和其他几种语言需要与默认语言不同的套管规则. (7认同)
  • @ user2864740:如果一个大写的希腊语tau与罗马字母T没有区别,那么希腊语和罗马语文字很难按字母顺序排序.此外,如果字体对希腊字母和罗马字母使用不同的视觉样式,如果形状类似于罗马字母的希腊字母与不具有罗马字母的形状不同,则会非常分散注意力. (4认同)

Sub*_*cob 34

搜索Unicode数据库中的两个字符并查看差异.

一个是希腊小写字母 µ,另一个是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)
Run Code Online (Sandbox Code Playgroud)
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)
Run Code Online (Sandbox Code Playgroud)

  • 实际上问题是另一个问题,问为什么μ和μ相等检查返回false.这个答案回答了它.后来OP问另一个问题(这个问题)如何比较两个看起来相似的角色.这两个问题都有最佳答案,后来其中一位主持人合并了两个问题,最好选择第二个问题的最佳答案.有人编辑了这个问题,以便总结 (5认同)
  • 这是如何获得37票的?它没有回答问题("如何比较unicode字符"),它只是评论为什么这个特定的例子不相等.充其量,它应该是对这个问题的评论.我理解评论格式化选项不允许像答案格式化选项那样发布它,但这不应该是作为答案发布的正当理由. (4认同)

dbw*_*dbw 24

编辑此问题合并后如何比较C中的'μ'和'μ'
原始答案发布:

 "?".ToUpper().Equals("µ".ToUpper()); //This always return true.
Run Code Online (Sandbox Code Playgroud)

编辑 阅读评论后,是的,使用上述方法并不好,因为它可能会为其他类型的输入提供错误的结果,为此我们应该使用维基中提到的完全兼容性分解进行规范化.(感谢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();
    }
Run Code Online (Sandbox Code Playgroud)

产量

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
 ________________________________________________________________
Run Code Online (Sandbox Code Playgroud)

在读取Unicode_equivalence中的信息时,我发现了

等价标准的选择会影响搜索结果.例如,一些印刷连字,如U + FB03(ffi),.....因此搜索 U + 0066(f)作为子串将成功进行U + FB03 的NFKC归一化,但不能成为U + FB03的NFC归一化.

因此,为了比较等价,我们通常应该使用FormKC即NFKC归一化或FormKD即NFKD归一化.
我很想知道关于所有Unicode字符的更多信息,所以我制作了可以迭代所有Unicode字符的示例,UTF-16我得到了一些我想讨论的结果

  • 有关字符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 '?' 453 '?' 454 '?' 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

  • 该解决方案掩盖了该问题,并且可能在一般情况下引起问题.这种测试会发现`"m".ToUpper().Equals("μ".ToUpper());`和""M".ToUpper().Equals("μ".ToUpper());`也是如此.这可能是不可取的. (45认同)
  • -1 - 这是一个糟糕的主意.不要像这样使用Unicode. (6认同)
  • 有一个很好的理由来区分"MICRO SIGN"和"GREEK SMALL LETTER MU" - 说微标志的"大写"仍然是微观标志.但是资本化变得微观,变成了巨大的,快乐的工程. (6认同)
  • 奇怪但是有效...我的意思是它们是两个不同含义的不同字符并将它们转换为上层使它们相等?我没有看到逻辑但很好的解决方案+1 (4认同)

PMF*_*PMF 9

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


hip*_*ail 6

你问"如何比较它们",但你没有告诉我们你想做什么.

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

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

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

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

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


use*_*710 5

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

首先,2个实体您要比较有glyphS,A字形是一组由什么通常知道作为一个"字体"提供的字形,这通常是在一个事物的一部分ttf,otf或任何文件格式,你是使用.

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

什么通常用来解决类似于你遇到的问题,它是一个OCR,本质上是一个识别和比较字形的软件,如果C#默认提供OCR我不知道,但它通常是一个非常糟糕的想法,如果你真的不需要OCR,你知道如何处理它.

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

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