Jak porównać znaki Unicode, które „wyglądają podobnie”?

94

Wpadam w zaskakujący problem.

Załadowałem plik tekstowy do mojej aplikacji i mam pewną logikę, która porównuje wartość mającą µ.

I zdałem sobie sprawę, że nawet jeśli teksty są takie same, wartość porównania jest fałszywa.

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

W dalszej linii wklejany jest znak µ.

Jednak mogą to nie być jedyne postacie, które są takie.

Czy istnieje sposób w C #, aby porównać znaki, które wyglądają tak samo, ale w rzeczywistości są różne?

DJ
źródło
158
Wygląda na to, że znalazłeś mu Schrödingera.
BoltClock
19
Są to różne postacie - mimo że wyglądają tak samo, mają różne kody znaków.
user2864740
93
Witamy w Unicode.
ta.speot.is
11
co chcesz osiągnąć że te dwa powinny być równe, to nawet ich kod znaków jest inny, ale ta sama twarz
Jade,
28
„Wyglądaj podobnie” i „wyglądaj tak samo” to niejasne pojęcia. Czy oznaczają tożsamość glifów, czy tylko bliskie podobieństwo? Jak blisko? Zwróć uwagę, że dwa znaki mogą mieć identyczne glify w jednej czcionce, bardzo podobne w innej i zupełnie niepodobne do innej czcionki. Liczy się, dlaczego zrobiłbyś takie porównanie iw jakim kontekście (oraz dopuszczalność fałszywych trafień i fałszywie negatywnych).
Jukka K. Korpela,

Odpowiedzi:

125

W wielu przypadkach można znormalizować oba znaki Unicode do określonej formy normalizacji przed ich porównaniem i powinny być w stanie dopasować. Oczywiście, jakiej formy normalizacji należy użyć, zależy od samych postaci; tylko dlatego, że wyglądają podobnie, niekoniecznie oznacza, że ​​reprezentują tę samą postać. Musisz także rozważyć, czy jest to odpowiednie dla twojego przypadku użycia - zobacz komentarz Jukka K. Korpela.

W tej konkretnej sytuacji, jeśli odniesiesz się do linków w odpowiedzi Tony'ego , zobaczysz, że tabela dla U + 00B5 mówi:

Dekompozycja <compat> GRECKA MAŁA LITERA MU (U + 03BC)

Oznacza to, że U + 00B5, drugi znak w oryginalnym porównaniu, można rozłożyć na U + 03BC, pierwszy znak.

Więc znormalizujesz znaki przy użyciu pełnej dekompozycji zgodności, z formami normalizacji KC lub KD. Oto krótki przykład, który napisałem, aby zademonstrować:

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

Szczegółowe informacje na temat normalizacji Unicode i różnych form normalizacji patrz System.Text.NormalizationFormi spec Unicode .

BoltClock
źródło
26
Dzięki za łącze do specyfikacji Unicode. Pierwszy raz przeczytałem o tym. Mała uwaga z tego: „Formy normalizacji KC i KD nie mogą być ślepo stosowane do dowolnego tekstu. Najlepiej jest myśleć o tych Formularzach normalizacji jako o odwzorowaniach wielkich lub małych liter: przydatne w pewnych kontekstach do identyfikacji podstawowych znaczeń, ale także modyfikacje tekstu, które nie zawsze są właściwe. ”
user2864740
149

Ponieważ są to naprawdę różne symbole, nawet jeśli wyglądają tak samo, pierwszy to właściwa litera i ma char, code = 956 (0x3BC)a drugi to mikro znak i ma181 (0xB5) .

Bibliografia:

Więc jeśli chcesz je porównać i chcesz, aby były równe, musisz to zrobić ręcznie lub zamienić jeden znak na inny przed porównaniem. Lub użyj następującego kodu:

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

I Demo

Tony
źródło
11
Z ciekawości, jaki jest powód posiadania dwóch symboli µ? Nie widzisz dedykowanego K z nazwą „znak Kilo” (czy nie?).
MartinHaTh
12
@MartinHaTh: Według Wikipedii to „z powodów historycznych” .
BoltClock
12
Unicode ma wiele znaków zgodności przeniesionych ze starszych zestawów znaków (takich jak ISO 8859-1 ), aby ułatwić konwersję z tych zestawów znaków. Wcześniej, gdy zestawy znaków były ograniczone do 8 bitów, zawierały kilka glifów (jak niektóre greckie litery) do najpowszechniejszych zastosowań matematycznych i naukowych. Powszechne było ponowne użycie glifów na podstawie wyglądu, więc nie dodano specjalistycznego „K”. Ale zawsze było to obejście; poprawnym symbolem „mikro” jest grecka mała litera mu, prawidłowym symbolem Ohm jest faktyczna wielka litera omega i tak dalej.
VGR
8
Nie ma nic lepszego niż zrobienie czegoś dla histerycznych rodzynek
paulm
11
Czy istnieje specjalne K dla płatków śniadaniowych?
86

Oba mają różne kody znaków: zapoznaj się z tym, aby uzyskać więcej informacji

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

Gdzie pierwszy to:

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

Wizerunek

Vishal Suthar
źródło
39

W konkretnym przykładzie μ(mu) i µ(mikro znak) ten drugi ma rozkład zgodności z pierwszym, dzięki czemu można znormalizować ciąg na FormKClub FormKDprzekształcić mikro znaki na mus.

Jednak istnieje wiele zestawów znaków, które wyglądają podobnie, ale nie są równoważne w żadnym formularzu normalizacji Unicode. Na przykład A(łaciński), Α(grecki) i А(cyrylica). Witryna Unicode zawiera plik confusables.txt z ich listą, który ma pomóc programistom chronić się przed atakami homografów . W razie potrzeby możesz przeanalizować ten plik i zbudować tabelę do „wizualnej normalizacji” łańcuchów.

dan04
źródło
Zdecydowanie dobrze wiedzieć podczas korzystania z Normalize. Wydaje się zaskakujące, że pozostają odrębne.
user2864740
4
@ user2864740: Gdyby wielkie greckie tau nie różniło się od rzymskiej litery T, byłoby bardzo trudno rozsądnie posortować tekst grecki i rzymski w porządku alfabetycznym. Ponadto, jeśli krój pisma miałby używać innego stylu wizualnego dla liter greckich i rzymskich, byłoby bardzo rozpraszające, gdyby greckie litery, których kształty przypominały litery rzymskie, były renderowane inaczej niż te, które tego nie robią.
supercat
7
Co ważniejsze, ujednolicenie alfabetów europejskich spowodowałoby ToUpper/ byłoby ToLowertrudne do wdrożenia. Musiałbyś "B".ToLower()być bw języku angielskim, ale βgreckim i вrosyjskim. Obecnie tylko turecki (bez kropek i) i kilka innych języków wymaga innych reguł wielkości liter niż domyślne.
dan04
@ dan04: Zastanawiam się, czy ktoś kiedykolwiek rozważał przypisanie unikalnych punktów kodowych wszystkim czterem odmianom tureckiego „i” oraz „I”? To wyeliminowałoby wszelkie niejednoznaczności w zachowaniu toUpper / toLower.
supercat
34

Wyszukaj oba znaki w bazie danych Unicode i zobacz różnicę .

Jedna to grecka mała litera, µ a druga to mikroznak µ .

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)
Subin Jacob
źródło
4
Skąd wzięło się 37 głosów pozytywnych? Nie odpowiada na pytanie („Jak porównać znaki Unicode”), a jedynie komentuje, dlaczego ten konkretny przykład nie jest równy. W najlepszym przypadku powinien to być komentarz do pytania. Rozumiem, że opcje formatowania komentarzy nie pozwalają na publikowanie ich tak ładnie, jak opcje formatowania odpowiedzi, ale to nie powinno być uzasadnionym powodem do publikowania jako odpowiedzi.
Konerak
5
Właściwie pytanie było inne, pytając, dlaczego kontrola równości μ i µ jest fałszywa. Ta odpowiedź odpowiada na nie. Później OP zadał kolejne pytanie (to pytanie), jak porównać dwa podobne postacie. Oba pytania miały najlepsze odpowiedzi, a później jeden z moderatorów połączył oba pytania, wybierając najlepszą odpowiedź na drugie. Ktoś zredagował to pytanie, aby podsumować
Subin Jacob
Właściwie nie dodałem żadnych treści po scaleniu
Subin Jacob
24

EDYTUJ Po połączeniu tego pytania z Jak porównać „μ” i „µ” w C #
Oryginalna odpowiedź opublikowana:

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

EDYTOWAĆ Po przeczytaniu komentarzy, tak, nie jest dobrze używać powyższej metody, ponieważ może ona dawać złe wyniki dla innego typu danych wejściowych, w tym celu powinniśmy użyć normalizacji z pełną dekompozycją zgodności, jak wspomniano na wiki . (Dzięki odpowiedzi przesłanej przez 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();
    }

Wynik

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
 ________________________________________________________________

Podczas czytania informacji w Unicode_equivalence znalazłem

Wybór kryteriów równoważności może wpłynąć na wyniki wyszukiwania. Na przykład niektóre ligatury typograficzne, takie jak U + FB03 (ffi), ..... więc wyszukiwanie U + 0066 (f) jako podłańcucha zakończy się powodzeniem w normalizacji NFKC U + FB03, ale nie w normalizacji NFC U + FB03.

Aby porównać równoważność, powinniśmy normalnie użyć FormKC np. Normalizacji NFKC lub FormKDnp. Normalizacji NFKD.
Byłem trochę ciekawy, aby dowiedzieć się więcej o wszystkich znakach Unicode, więc stworzyłem próbkę, która będzie iterować po wszystkich znakach Unicode w UTF-16i otrzymałem wyniki, które chcę omówić

  • Informacje o postaciach, których FormC i FormDznormalizowane wartości nie były równoważne
    Total: 12,118
    Character (int value): 192-197, 199-207, 209-214, 217-221, 224-253, ..... 44032-55203
  • Informacje o postaciach, których FormKC i FormKDznormalizowane wartości nie były równoważne
    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
  • Wszystkie postacie, których FormCiFormD znormalizowana wartość nie były równoważne, tam FormKCi FormKDznormalizowane wartości również nie były równoważne, z wyjątkiem tych znaków
    .901 '΅', 8129 '῁', 8141 '῍', 8142 '῎', 8143 '῏', 8157 '῝', 8158 '῞'
    , 8159 '῟', 8173 '῭', 8174 '΅'
  • Dodatkowy znak, którego FormKCiFormKD znormalizowana wartość nie były równoważne, ale tam FormCi FormDznormalizowane wartości były równoważne
    Total: 119
    Znaki: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'
  • Są postacie, których nie można znormalizować , rzucająArgumentException jeśli spróbują
    Total:2081 Characters(int value): 55296-57343, 64976-65007, 65534

Te linki mogą być bardzo pomocne w zrozumieniu, jakie reguły rządzą równoważnością Unicode

  1. Równoważność_ Unicode
  2. Unicode_compatibility_characters
dbw
źródło
4
Dziwne, ale działa ... Mam na myśli to, że są to dwa różne znaki o różnych znaczeniach i zamiana ich na wyższe sprawia, że ​​są równe? Nie widzę logiki, ale fajne rozwiązanie +1
BudBrot
45
To rozwiązanie maskuje problem i może powodować problemy w ogólnym przypadku. Tego rodzaju test by to wykazał "m".ToUpper().Equals("µ".ToUpper());i "M".ToUpper().Equals("µ".ToUpper());jest również prawdziwy. To może być niepożądane.
Andrew Leach,
6
-1 - to okropny pomysł. Nie pracuj z Unicode w ten sposób.
Konrad Rudolph
1
Zamiast sztuczek opartych na ToUpper (), dlaczego nie użyć String.Equals ("μ", "μ", StringComparison.CurrentCultureIgnoreCase)?
svenv
6
Jest jeden dobry powód, aby odróżnić „MIKROZNAK” od „GREEK MAŁA LITERA MU” - powiedzieć, że „wielka litera” mikroznaku jest nadal mikro znakiem. Ale kapitalizacja zmienia mikro w mega, szczęśliwą inżynierię.
Greg,
9

Najprawdopodobniej istnieją dwa różne kody znaków, które tworzą (widocznie) ten sam znak. Chociaż technicznie nie są równe, wyglądają na równe. Spójrz na tabelę znaków i zobacz, czy istnieje wiele instancji tej postaci. Lub wydrukuj kod dwóch znaków w swoim kodzie.

PMF
źródło
6

Pytasz „jak je porównać”, ale nie mówisz nam, co chcesz robić.

Istnieją co najmniej dwa główne sposoby ich porównania:

Albo porównasz je bezpośrednio, jak jesteś, i są różne

Lub możesz użyć Normalizacji zgodności Unicode, jeśli potrzebujesz porównania, które znajdzie je pasujące.

Może jednak wystąpić problem, ponieważ normalizacja zgodności Unicode sprawi, że porównanie wielu innych znaków będzie równe. Jeśli chcesz, aby tylko te dwa znaki były traktowane tak samo, powinieneś rzucić własne funkcje normalizacji lub porównania.

Aby uzyskać bardziej szczegółowe rozwiązanie, musimy znać Twój konkretny problem. W jakim kontekście natknąłeś się na ten problem?

hippietrail
źródło
1
Czy „znak mikro” i mała litera mu są kanonicznie równoważne? Użycie normalizacji kanonicznej zapewniłoby bardziej ścisłe porównanie.
Tanner Swett
@ TannerL.Swett: Właściwie nie jestem nawet pewien, jak to sprawdzić z czubka głowy ...
hippietrail
1
Właściwie to importowałem plik ze wzorem fizyki. Masz rację co do normalizacji. Muszę przejść przez to głębiej ...
DJ
Jaki rodzaj pliku? Coś ręcznie wykonanego przez osobę w zwykłym tekście Unicode? A może coś, co aplikacja wyświetla w określonym formacie?
hippietrail
5

Jeśli chciałbym być pedantyczny, powiedziałbym, że twoje pytanie nie ma sensu, ale ponieważ zbliżamy się do Bożego Narodzenia, a ptaki śpiewają, przejdę do tego.

Po pierwsze, 2 encje, które próbujesz porównać, to glyphs, glif jest częścią zestawu glifów dostarczanych przez coś, co zwykle nazywa się „czcionką”, czymś, co zwykle występuje w postaci ttf,otf lub jakiegoś pliku w formacie jesteś za pomocą.

Glify są reprezentacją danego symbolu, a ponieważ są one reprezentacją zależną od określonego zestawu, nie możesz po prostu oczekiwać, że będą miały 2 podobne lub nawet „lepsze” identyczne symbole, to wyrażenie, które nie ma sensu jeśli weźmiesz pod uwagę kontekst, powinieneś przynajmniej określić, jaką czcionkę lub zestaw glifów bierzesz pod uwagę, formułując takie pytanie.

To, co jest zwykle używane do rozwiązania problemu podobnego do tego, z którym się spotykasz, to OCR, zasadniczo oprogramowanie, które rozpoznaje i porównuje glify, jeśli C # zapewnia OCR domyślnie , nie wiem tego, ale ogólnie jest to naprawdę złe pomysł, jeśli tak naprawdę nie potrzebujesz OCR i wiesz, co z nim zrobić.

Możesz skończyć z interpretacją książki o fizyce jako starożytnej greckiej książki, nie wspominając o tym, że OCR są generalnie drogie pod względem zasobów.

Jest powód, dla którego te postacie są zlokalizowane w taki sposób, w jaki są zlokalizowane, po prostu tego nie rób.

user2485710
źródło
1

Za pomocą można narysować oba znaki o tym samym stylu i rozmiarze czcionki DrawString metody . Po wygenerowaniu dwóch bitmap z symbolami można je porównać piksel po pikselu.

Zaletą tej metody jest to, że można porównać nie tylko absolutnie równe znaki, ale także podobne (z określoną tolerancją).

Ivan Kochurkin
źródło