Як порівняти символи Unicode, які «схожі»?


94

Я потрапляю в дивовижне питання.

Я завантажив текстовий файл у свою програму і маю певну логіку, яка порівнює значення, що має µ.

І я зрозумів, що навіть якщо тексти однакові, значення порівняння є хибним.

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

У наступному рядку символ µ копіюється.

Однак це можуть бути не єдині персонажі, які подібні до цього.

Чи є в C # спосіб порівняти символи, які виглядають однаково, але насправді різні?


158
Схоже, ви знайшли му Шредінгера.
BoltClock

19
Вони різні символи - хоча вони виглядають однаково, вони мають різні коди символів.
користувач2864740

93
Ласкаво просимо до Unicode.
ta.speot.is

11
чого ти хочеш досягти? що ці двоє мають бути рівними, то навіть код їх символів різний, але однакова грань?
Джейд

28
"Подібний вигляд" і "вигляд однаковий" - це розмиті поняття. Вони означають ідентичність гліфів або просто близьку схожість? Наскільки близько? Зверніть увагу, що два символи можуть мати однакові гліфи в якомусь шрифті, дуже схожі в іншому і зовсім несхожі в іншому шрифті. Важливо те, чому ви робите таке порівняння і в якому контексті (і прийнятність помилково позитивних та помилкових негативів).
Jukka K. Korpela

Відповіді:


125

У багатьох випадках ви можете нормалізувати обидва символи Unicode до певної форми нормалізації перед їх порівнянням, і вони повинні мати можливість збігатися. Звичайно, яку форму нормалізації потрібно використовувати, залежить від самих персонажів; тільки тому , що вони виглядають однаково , не обов'язково означає , що вони являють собою один і той же характер. Вам також слід подумати, чи це підходить для вашого випадку використання - див. Коментар Юкки К. Корпели.

У цій конкретній ситуації, якщо ви звернетесь до посилань у відповіді Тоні , ви побачите, що в таблиці для 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.NormalizationFormі специфікація Unicode .


26
Дякуємо за специфікацію Unicode. Вперше я колись читав це. Невелика примітка з нього: "Форми нормалізації KC і KD не слід застосовувати наосліп до довільного тексту. Найкраще думати про ці форми нормалізації як про подібні великі або малі відображення: корисні в певному контексті для ідентифікації основних значень, а також для виконання зміни тексту, які не завжди можуть бути доречними ".
користувач2864740

149

Оскільки це справді різні символи, навіть якщо вони виглядають однаково, перший - це власне буква і має 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

12
@MartinHaTh: За даними Вікіпедії, це "з історичних причин" .
BoltClock

12
Unicode має багато символів сумісності, отриманих із старих наборів символів (наприклад, ISO 8859-1 ), щоб полегшити перетворення з цих наборів символів. Назад, коли набори символів обмежувались 8 бітами, вони включали кілька гліфів (як деякі грецькі літери) для найбільш поширених математичних та наукових цілей. Повторне використання гліфів на основі зовнішнього вигляду було поширеним явищем, тому жодного спеціалізованого "К" не додавали. Але це завжди було обхідним шляхом; правильний символ для "мікро" - це власне грецька мала літера, mu, правильний символ для Ома - фактична велика омега тощо.
VGR

8
Нічого кращого, ніж коли щось роблять для істеричного родзинок
Полм

11
Чи існує спеціальний К для крупи?


39

Для конкретного прикладу μ(mu) та µ(micro sign) останній має декомпозицію сумісності з першим, тому ви можете нормалізувати рядок до FormKCабо FormKDперетворити мікрознаки в mus.

Однак існує безліч наборів символів, які схожі, але не еквівалентні в будь-якій формі нормалізації Unicode. Наприклад, A(латиниця), Α(грецька) та А(кирилиця). На веб-сайті Unicode є файл confusables.txt із переліком цих файлів, призначений допомогти розробникам захиститися від атак гомографа . За необхідності ви можете проаналізувати цей файл і створити таблицю для “візуальної нормалізації” рядків.


Безумовно, добре це знати, використовуючи Normalize. Дивно, що вони залишаються чіткими.
користувач2864740

4
@ user2864740: Якби велика грецька тау не залишалася відмінною від римської літери Т, було б дуже важко мати розумний сортування грецького та римського тексту в алфавітному порядку. Крім того, якби шрифт використовував інший візуальний стиль для грецьких та римських букв, це було б дуже відволікаючим, якби грецькі літери, форми яких нагадували римські літери, передавалися інакше, ніж ті, що цього не робили.
supercat

7
Що ще важливіше, уніфікація європейських алфавітів ускладнить ToUpper/ ToLowerускладнить впровадження. Ви повинні були б мати "B".ToLower()бути bанглійською мовою , але βна грецькому і вросійською мовами. В даний час лише турецька (без дотла i) та ще декілька інших мов потребують правил кодування, відмінних від стандартних.
dan04

@ dan04: Цікаво, чи коли-небудь хтось замислювався про присвоєння унікальних точок коду всім чотирьом варіаціям турецького "i" та "I"? Це усунуло б будь-яку неясність у поведінці toUpper / toLower.
supercat

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"), він просто коментує, чому цей конкретний приклад не рівний. У кращому випадку це повинен бути коментар до питання. Я розумію, що параметри форматування коментарів не дозволяють опублікувати його так добре, як це роблять варіанти форматування відповідей, але це не повинно бути вагомою причиною для розміщення відповіді.
Конерак

5
Насправді питання було іншим, запитуючи, чому перевірка рівності μ і µ повертає false. Ця відповідь дайте відповідь. Пізніше ОП задав інше запитання (це питання), як порівняти двох символів, схожих один на одного. Обидва питання мали найкращі відповіді, а пізніше один із модераторів об’єднав обидва питання, вибравши найкращу відповідь на друге як найкраще. Хтось відредагував це запитання, щоб воно підбило підсумок
Субін Яків

Власне, я не додав жодного вмісту після злиття
Subin Jacob

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.

Тому для порівняння еквівалентності ми зазвичай повинні використовувати, FormKCтобто нормалізацію NFKC або FormKDнормалізацію NFKD.
Мені було трохи цікаво дізнатись більше про всі символи Unicode, тому я зробив зразок, який перебирав би всі символи Unicode, UTF-16і я отримав деякі результати, які хочу обговорити

  • Інформація про символи , чиї 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'
  • Є деякі символи, які неможливо нормалізувати , вони кидаються, 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
Є одна вагома причина, щоб розрізнити "МІКРОЗНАК" та "ГРЕЦЬКИЙ МАЛИЙ ЛІТЕР МУ" - сказати, що "велика літера" мікрознака все ще є мікрознаком. Але капіталізація змінює мікро на мега, щаслива техніка.
Грег

9

Швидше за все, є два різних коди символів, які роблять (помітно) однаковим символом. Хоча технічно вони не рівні, вони виглядають рівними. Погляньте на таблицю символів і перевірте, чи є кілька екземплярів цього символу. Або роздрукуйте код символу двох символів у вашому коді.


6

Ви запитуєте "як їх порівняти", але ви не кажете нам, що ви хочете зробити.

Існує щонайменше два основних способи їх порівняння:

Або ви порівнюєте їх безпосередньо такими, які ви є, і вони різні

Або ви використовуєте нормалізацію сумісності Unicode, якщо вам потрібне порівняння, яке визначає їх відповідність.

Однак може виникнути проблема, оскільки нормалізація сумісності Unicode зробить рівними багато інших символів. Якщо ви хочете, щоб лише ці два символи розглядались однаково, вам слід прокрутити власні функції нормалізації або порівняння.

Для більш конкретного рішення нам потрібно знати вашу конкретну проблему. У якому контексті ви стикалися з цією проблемою?


1
Чи є "мікро знак" та символ малих літер mu канонічно рівнозначними? Використання канонічної нормалізації дасть вам більш суворе порівняння.
Tanner Swett

@ TannerL.Swett: Насправді я навіть не впевнений, як це перевірити на маківці ...
hippietrail

1
Власне, я імпортував файл із формулою фізики. Ви маєте рацію щодо нормалізації. Я повинен пройти через це глибше ..
DJ

Що це за файл? Щось зроблене власноруч у простому тексті Unicode людиною? Або щось, що виводиться додатком у певному форматі?
hippietrail

5

Якщо я хотів би бути педантичним, я б сказав, що ваше запитання не має сенсу, але оскільки ми наближаємося до Різдва, а птахи співають, я продовжу з цим.

По- перше, на 2 особи , які ви намагаєтеся порівняти це glyphз, гліф є частиною набору символів , що надаються тим , що, як правило , знають , як «шрифт», то , що зазвичай приходить в ttf, otfабо будь-який інший формат файлу , ви використання.

Гліфи - це подання даного символу, і оскільки вони є поданням, яке залежить від конкретного набору, ви не можете просто розраховувати на наявність двох подібних або навіть "кращих" однакових символів, це фраза, яка не має сенсу якщо ви враховуєте контекст, вам слід принаймні вказати, який шрифт чи набір гліфів ви розглядаєте, формулюючи таке питання.

Що зазвичай використовується для вирішення проблеми, подібної до тієї, з якою ви стикаєтесь, це OCR, по суті програмне забезпечення, яке розпізнає та порівнює гліфи. Якщо C # забезпечує OCR за замовчуванням, я цього не знаю, але загалом це дуже погано ідея, якщо вам дійсно не потрібен OCR і ви знаєте, що з ним робити.

Можливо, ви в результаті зможете інтерпретувати книгу з фізики як давньогрецьку книгу, не згадуючи того факту, що OCR, як правило, дорогі з точки зору ресурсів.

Існує причина, чому ці символи локалізовані так, як вони локалізовані, просто не робіть цього.


1

За допомогою DrawStringметоду можна намалювати обидва символи з однаковим стилем шрифту та розміром . Після створення двох растрових зображень із символами можна порівнювати їх піксель за пікселем.

Перевага цього методу полягає в тому, що ви можете порівнювати не тільки абсолютно рівні символи, але й подібні (з певним допуском).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.