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


94

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

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

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

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

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

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

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


158
您似乎已经找到了薛定ding的亩。
BoltClock

19
它们是不同的字符-即使它们看起来相同,也具有不同的字符代码。
user2864740

93
欢迎使用Unicode。
ta.speot。是

11
您想实现什么?那两个应该相等,那么即使他们的字符代码是不同的,却是相同的面孔?
翡翠

28
“相似”和“相同”是模糊的概念。它们是指字形的身份,还是仅仅相似?多近?请注意,两个字符在某些字体中可能具有相同的字形,在另一种字体中可能非常相似,而在另一种字体中却完全不同。重要的是为什么要进行这样的比较以及在哪种情况下(以及误报和误报的可接受性)。
Jukka K. Korpela 2013年

Answers:


125

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

对于这种特殊情况,如果您参考Tony的答案中的链接,则会看到U + 00B5的表显示:

分解<compat>希腊小写字母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.NormalizationFormUnicode规范


26
感谢您的Unicode规范链接。我第一次读过它。它的一个小注释:“规范化形式KC和KD不能盲目地应用于任意文本。对文本的修改可能并不总是合适的。”
user2864740 2013年

149

因为即使它们看起来相同,也确实是不同的符号,第一个是实际字母并具有char 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);
}

演示


11
出于好奇,拥有两个µ符​​号的原因是什么?您不会看到名称为“公斤符号”的专用K(或者,是吗?)。
MartinHaTh 2013年

12
@MartinHaTh:据Wikipedia称,这是“出于历史原因”
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

其中,第一个是:

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

图片


39

对于μ(mu)和µ(micro sign)的特定示例,后者具有与前者的兼容性分解,因此您可以将字符串标准化FormKCFormKD将微符号转换为mus。

但是,有许多相似的字符集,但是在任何Unicode规范化形式下都不相同。例如,A(拉丁),Α(希腊)和А(西里尔字母)。Unicode网站上有一个confusables.txt文件,其中包含这些文件的列表,目的是帮助开发人员防御同形异义词攻击。如有必要,您可以解析此文件并构建一个表以对字符串进行“可视化标准化”。


使用Normalize时绝对很了解。它们保持不同似乎令人惊讶。
user2864740

4
@ user2864740:如果大写希腊字母tau不能与罗马字母T保持区别,那么很难使希腊和罗马文本合理地按字母顺序排序。此外,如果字体要对希腊字母和罗马字母使用不同的视觉样式,那么如果形状类似于罗马字母的希腊字母的呈现方式与没有罗马字母的希腊字母的呈现方式有所不同,那将非常令人分心。
2013年

7
更重要的是,统一欧洲字母将使ToUpper/ ToLower难以实施。你需要有"B".ToLower()可以b在英语,但β在希腊和в俄罗斯。实际上,只有土耳其语(dotless i)和其他几种语言需要的大小写规则与默认规则不同。
dan04

@ dan04:我想知道是否有人考虑为土耳其语“ i”和“ I”的所有四个变体分配唯一的代码点?这样可以消除toUpper / toLower行为中的任何歧义。
2014年

34

Unicode数据库中搜索两个字符,然后看一下区别

一个是希腊小写字母 µ,另一个是微标志 µ

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提出了另一个问题(该问题),该如何比较两个相似的字符。两个问题都有最佳答案,后来一位主持人合并了两个问题,选择了第二个问题的最佳答案。有人编辑了这个问题,以便对其进行总结
Subin Jacob 2013年

实际上,合并后我没有添加任何内容
Subin Jacob 2013年

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归一化或FormKDNFKD归一化。
我不太想知道更多关于所有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 '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
奇怪但有效...我的意思是它们是两个不同的字符,具有不同的含义,并将它们转换为上位字符会使它们相等?我看不到逻辑,但不错的解决方案+1
BudBrot

45
此解决方案掩盖了该问题,在一般情况下可能会引起问题。这种测试会发现这一点"m".ToUpper().Equals("µ".ToUpper());"M".ToUpper().Equals("µ".ToUpper());并且也是正确的。这可能不是理想的。
安德鲁·里奇

6
-1 –这是一个糟糕的主意。不能像这样使用Unicode。
康拉德·鲁道夫

1
除了使用基于ToUpper()的技巧外,为什么不使用String.Equals(“μ”,“μ”,StringComparison.CurrentCultureIgnoreCase)?
svenv

6
区分“ MICRO SIGN”和“ GREEK SMALL LETTER MU”有一个很好的理由-说微符号的“大写”仍然是微符号。但是大写字母将微观变成巨型的,令人满意的工程。
格雷格

9

最有可能的是,有两种不同的字符代码(明显地)构成同一字符。尽管从技术上讲不相等,但它们看起来相等。查看字符表,看看该字符是否有多个实例。或在代码中打印出两个字符的字符代码。


6

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

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

您要么直接比较它们,要么就不同了

或者,如果需要进行比较以找到匹配的匹配项,则可以使用Unicode兼容性规范化。

但是可能存在问题,因为Unicode兼容性规范化将使许多其他字符比较相等。如果只希望将这两个字符视为相同,则应滚动自己的规范化或比较函数。

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


1
“微符号”和小写的mu字符规范上相等吗?使用规范化规范化将使您进行更严格的比较。
Tanner Swett

@ TannerL.Swett:实际上,我什至不知道该如何检查我的头部……
hippietrail 2013年

1
实际上,我正在导入具有物理公式的文件。您对规范化是正确的。我必须更深入地研究它
DJ

什么样的文件?一个人用纯Unicode文本手工制作的东西吗?还是应用以特定格式输出的内容?
hippietrail

5

如果我想做个书呆子,我想说您的问题没有道理,但是由于我们临近圣诞节,而鸟儿也在唱歌,因此我将继续进行下去。

首先,您要比较的2个实体是glyphs,一个字形是通常由“字体”提供的一组字形的一部分,通常以来提供ttfotf或者是您使用的任何文件格式使用。

字形是给定符号的表示形式,并且由于它们是取决于特定集合的表示形式,因此您不能仅期望拥有2个相似甚至“更好”的相同符号,这是一个没有意义的短语如果考虑到上下文,则在制定这样的问题时,至少应指定要考虑的字体或字形集。

通常用于解决与您遇到的问题类似的问题的工具,它是OCR,本质上是一种识别和比较字形的软件,如果C#提供了OCR,默认,我不知道,但是通常这是一个很糟糕的做法知道您是否真的不需要OCR,并且知道如何使用它。

您可能最终将一本物理学书籍解释为一本古希腊书籍,而没有提及OCR在资源方面通常很昂贵的事实。

这些字符按照其本地化方式进行本地化是有原因的,只是不要这样做。


1

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

这种方法的优势在于,您不仅可以比较绝对相等的字符,还可以比较相似的字符(具有确定的容差)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.