如何比较“看起来相似”的Unicode字符?
我陷入了一个令人惊讶的问题。
我在我的应用程序中加载了一个文本文件,我有一些比较μ值的逻辑。
而且我意识到,即使文本相同,比较值也是错误的。
Console.WriteLine("μ".Equals("µ")); // returns false Console.WriteLine("µ".Equals("µ")); // return true
在后面的行中,字符μ被复制粘贴。
但是,这些可能不是唯一的字符。
在C#中有什么方法来比较看起来相同但实际上不同的字符?
在许多情况下,您可以在比较之前将两个Unicode字符归一化为特定的规范化表单,并且它们应该能够匹配。 当然,你需要使用哪种规范化forms取决于angular色本身; 只是因为它们看起来相似并不一定意味着它们代表了相同的性格。 您还需要考虑是否适合您的使用情况 – 请参阅Jukka K. Korpela的评论。
对于这个特殊的情况,如果你参考Tony的答案中的链接,你会发现U + 00B5的表格说:
分解<compat>希腊小字母MU(U + 03BC)
这意味着U + 00B5,原始比较中的第二个字符,可以分解为U + 03BC,第一个字符。
所以你会使用完全兼容性分解对字符进行规范化,使用规范化formsKC或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规范化和不同规范化forms的详细信息,请参阅System.Text.NormalizationForm
和Unicode规范 。
因为它们确实是不同的符号,即使它们看起来相同,首先是实际的字母,并且具有字符code = 956 (0x3BC)
,第二个是微型符号并具有181 (0xB5)
。
参考文献:
- Unicode字符'希腊小字母MU'(U + 03BC)
- Unicode字符'MICRO SIGN'(U + 00B5)
所以如果你想比较它们,你需要它们是平等的,你需要手动处理它,或者在比较之前用另一个字符replace一个字符。 或者使用下面的代码:
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 ==================================================================== μ μ μ μ Lowercase Mu µ µ µ µ micro sign Mu
对于μ
(μ)和µ
(微符号)的具体例子,后者与前者具有兼容性分解 ,因此您可以将string规范化为FormKC
或FormKD
以将微标志转换为mus。
但是,在任何Unicode规范化forms下,有许多字符集看起来相似,但并不等同。 例如, A
(拉丁语), A
(希腊语)和А
(西里尔语)。 Unicode网站有一个confusables.txt文件,其中列出了这些文件,旨在帮助开发人员防范同形异义(homograph)攻击 。 如果有必要的话,你可以parsing这个文件并为string的“可视化规范化”build立一个表格。
在Unicode数据库中 search这两个字符,并查看其差异 。
一个是希腊字母 µ
,另一个是Micro µ
。
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)
编辑这个问题合并后, 如何比较“μ”和“μ”在C#
原帖回复:
"μ".ToUpper().Equals("µ".ToUpper()); //This always return true.
编辑阅读注释后,是的,这是不好的,因为它可能会提供一些其他types的input错误的结果,因此,我们应该使用维基中提到的完全兼容性分解使用规范化 。 (感谢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_eivaivalence信息时,我发现
等值标准的select会影响search结果。 例如U + FB03(ffi),…这样的U + 0066(f)这样的印刷string,在U + FB03的NFKC标准化中search 成功 ,但U + FB03的NFC标准化不成功 。
所以为了比较等价性,我们通常应该使用FormKC
即NFKC标准化或FormKD
即NFKD标准化。
我有点好奇,想知道更多关于所有的Unicode字符,所以我做了一个样本,它将遍历UTF-16
所有Unicode字符,并且我得到了一些我想讨论的结果
- 关于
FormC
和FormD
标准化值不相同的字符的信息
Total: 12,118
Character (int value): 192-197, 199-207, 209-214, 217-221, 224-253, ..... 44032-55203
- 关于
FormKC
和FormKD
标准化值的字符信息不相同
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
-
FormC
和FormD
标准化值的所有字符都不相同,FormKC
和FormKD
标准化值也是不相同的,除了这些字符
字符:901 '΅', 8129 '῁', 8141 '῍', 8142 '῎', 8143 '῏', 8157 '῝', 8158 '῞'
, 8159 '῟', 8173 '῭', 8174 '΅'
-
FormKC
和FormKD
归一化值的额外字符不等价,但是FormC
和FormD
归一化值是等价的
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'
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
exception
Total:2081
Characters(int value): 55296-57343, 64976-65007, 65534
这个链接对于理解Unicode等价的规则是非常有帮助的
- Unicode_equivalence
- Unicode_compatibility_characters
最有可能的是,有两个不同的字符代码,使(可见)相同的字符。 尽pipe技术上并不相同,但它们看起来是平等的 看看angular色表,看看是否有多个angular色的实例。 或者在代码中打印两个字符的字符代码。
你问“如何比较”,但你不告诉我们你想做什么。
至less有两种主要的方法来比较它们:
要么你直接比较它们,因为它们是不同的
或者,如果您需要进行比较以find匹配,则使用Unicode兼容性标准化。
虽然Unicode兼容性规范化会使许多其他字符比较相同,但可能会有问题。 如果你只想把这两个字符看作是一样的,你应该推出你自己的规范化或比较函数。
对于更具体的解决scheme,我们需要了解您的具体问题。 你遇到这个问题的背景是什么?
如果我想迂腐,我会说你的问题没有意义,但是因为我们正在接近圣诞节,鸟儿在唱歌,所以我会继续这样做。
首先,你试图比较的2个实体是glyph
,字形是通常被称为“字体”的一组字形的一部分,通常在ttf
, otf
或任何文件中出现你正在使用的格式。
字形是给定符号的表示forms,由于它们是依赖于特定集合的表示forms,所以不能期望具有2个相似或甚至更好的相同符号,这是一个没有意义的短语如果你考虑上下文,你至less应该在制定这样一个问题时指定你正在考虑的字体或字形集。
什么是通常用来解决类似的问题,你遇到的,这是一个OCR,本质上是一个软件,识别和比较字形,如果C#默认提供了一个OCR我不知道,但它通常是一个非常糟糕的想法,如果你不需要一个OCR,你知道如何处理它。
你可能最终将一本物理学书籍解释为一本古希腊的书,而没有提到OCR在资源方面通常是昂贵的。
有一个原因是为什么这些字符是本地化的方式,只是不这样做。
使用DrawString
方法可以绘制两个字体样式和大小相同的字符。 在生成了两个带有符号的位图之后,可以逐个像素地进行比较。
这种方法的好处是,你不仅可以比较绝对相等的字符,而且可以比较相似(具有一定的容忍度)。