Come confrontare i caratteri Unicode che "si assomigliano"?


94

Cado in un problema sorprendente.

Ho caricato un file di testo nella mia applicazione e ho una logica che confronta il valore che ha µ.

E ho capito che anche se i testi sono gli stessi, il valore di confronto è falso.

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

Nella riga successiva viene copiato il carattere µ.

Tuttavia, questi potrebbero non essere gli unici personaggi che sono così.

C'è un modo in C # per confrontare i caratteri che sembrano uguali ma in realtà sono diversi?


158
Sembra che tu abbia trovato il mu di Schrödinger.
BoltClock

19
Sono caratteri diversi - anche se hanno lo stesso aspetto, hanno codici di caratteri diversi.
user2864740

93
Benvenuto in Unicode.
ta.speot.is

11
cosa vuoi ottenere? che quei due dovrebbero essere uguali quindi anche il loro codice carattere è diverso ma la stessa faccia?
Jade

28
"Guarda allo stesso modo" e "guarda allo stesso modo" sono concetti vaghi. Significano l'identità dei glifi o semplicemente una stretta somiglianza? Come chiudere? Nota che due caratteri possono avere glifi identici in alcuni caratteri, molto simili in un altro e abbastanza dissimili in un altro carattere. Ciò che conta è perché faresti un simile confronto e in quale contesto (e l'accettabilità di falsi positivi e falsi negativi).
Jukka K. Korpela

Risposte:


125

In molti casi, puoi normalizzare entrambi i caratteri Unicode in una determinata forma di normalizzazione prima di confrontarli e dovrebbero essere in grado di corrispondere. Ovviamente, il modulo di normalizzazione da utilizzare dipende dai personaggi stessi; solo perché si assomigliano non significa necessariamente che rappresentino lo stesso personaggio. Devi anche considerare se è appropriato per il tuo caso d'uso - vedi il commento di Jukka K. Korpela.

Per questa particolare situazione, se fai riferimento ai link nella risposta di Tony , vedrai che la tabella per U + 00B5 dice:

Decomposizione <compat> GRECO PICCOLA LETTERA MU (U + 03BC)

Ciò significa che U + 00B5, il secondo carattere nel confronto originale, può essere scomposto in U + 03BC, il primo carattere.

Quindi normalizzerai i caratteri usando la decomposizione della piena compatibilità, con le forme di normalizzazione KC o KD. Ecco un rapido esempio che ho scritto per dimostrare:

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

Per i dettagli sulla normalizzazione Unicode e le diverse forme di normalizzazione fare riferimento System.Text.NormalizationForme le specifiche Unicode .


26
Grazie per il collegamento alle specifiche Unicode. La prima volta che l'ho letto. Piccola nota da esso: "I moduli di normalizzazione KC e KD non devono essere applicati ciecamente a testo arbitrario .. È meglio pensare a questi moduli di normalizzazione come a mappature maiuscole o minuscole: utili in determinati contesti per identificare i significati fondamentali, ma anche per eseguire modifiche al testo che potrebbero non essere sempre appropriate. "
user2864740

149

Poiché sono simboli davvero diversi anche se sembrano uguali, il primo è la lettera effettiva e ha il carattere code = 956 (0x3BC)e il secondo è il micro segno e ha 181 (0xB5).

Riferimenti:

Quindi, se vuoi confrontarli e hai bisogno che siano uguali, devi gestirlo manualmente o sostituire un carattere con un altro prima del confronto. Oppure usa il codice seguente:

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

E la demo


11
Per curiosità, qual è il motivo per avere due simboli µ? Non vedi una K dedicata con il nome "Kilo sign" (o tu?).
MartinHaTh

12
@MartinHaTh: Secondo Wikipedia, è "per ragioni storiche" .
BoltClock

12
Unicode ha molti caratteri di compatibilità trasferiti da set di caratteri precedenti (come ISO 8859-1 ), per rendere più facile la conversione da quei set di caratteri. Ai tempi in cui i set di caratteri erano limitati a 8 bit, includevano alcuni glifi (come alcune lettere greche) per gli usi matematici e scientifici più comuni. Il riutilizzo dei glifi in base all'aspetto era comune, quindi non è stata aggiunta alcuna "K" specializzata. Ma è sempre stata una soluzione alternativa; il simbolo corretto per "micro" è l'attuale mu minuscolo greco, il simbolo corretto per Ohm è l'effettivo omega maiuscolo, e così via.
VGR

8
Niente di meglio di quando si fa qualcosa per l'isterica uvetta
paulm

11
Esiste una K speciale per i cereali?

86

Entrambi hanno codici di caratteri diversi: fare riferimento a questo per maggiori dettagli

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

Dove, il primo è:

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

Immagine


39

Per l'esempio specifico di μ(mu) e µ(micro segno), quest'ultimo ha una scomposizione di compatibilità con il primo, quindi è possibile normalizzare la stringa FormKCo FormKDconvertire i micro segni in mus.

Tuttavia, ci sono molti set di caratteri che si assomigliano ma non sono equivalenti in nessun modulo di normalizzazione Unicode. Ad esempio, A(latino), Α(greco) e А(cirillico). Il sito Web Unicode ha un file confusables.txt con un elenco di questi, destinato ad aiutare gli sviluppatori a proteggersi dagli attacchi di omografia . Se necessario, è possibile analizzare questo file e creare una tabella per la "normalizzazione visiva" delle stringhe.


Sicuramente buono da sapere quando si utilizza Normalize. Sembra sorprendente che rimangano distinti.
user2864740

4
@ user2864740: Se una tau greca maiuscola non fosse rimasta distinta da una lettera T romana, sarebbe molto difficile ordinare in modo ragionevole il testo greco e quello romano in ordine alfabetico. Inoltre, se un carattere tipografico usasse uno stile visivo diverso per le lettere greche e romane, sarebbe molto fastidioso se le lettere greche le cui forme assomigliano a lettere romane fossero rese in modo diverso da quelle che non lo facevano.
supercat

7
Ancora più importante, unificare gli alfabeti europei renderebbe ToUpper/ ToLowerdifficile da implementare. Dovresti "B".ToLower()essere bin inglese ma βin greco e вin russo. Così com'è, solo il turco (senza punti i) e un paio di altre lingue richiedono regole di maiuscole e minuscole diverse da quelle predefinite.
dan04

@ dan04: Mi chiedo se qualcuno abbia mai pensato di assegnare punti di codice univoci a tutte e quattro le varianti della "i" e della "I" turca? Ciò avrebbe eliminato qualsiasi ambiguità nel comportamento di toUpper / toLower.
supercat

34

Cerca entrambi i caratteri in un database Unicode e osserva la differenza .

Uno è la minuscola lettera greca µ e l'altro è il micro segno µ .

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
In che modo questo ha ottenuto 37 voti positivi? Non risponde alla domanda ("Come confrontare i caratteri Unicode"), commenta semplicemente perché questo particolare esempio non è uguale. Nella migliore delle ipotesi, dovrebbe essere un commento sulla domanda. Capisco che le opzioni di formattazione dei commenti non consentono di postarlo così bene come le opzioni di formattazione delle risposte, ma questo non dovrebbe essere un motivo valido per pubblicare come risposta.
Konerak

5
In realtà la domanda era diversa, chiedendo perché il controllo di uguaglianza μ e µ restituisce falso. Questa risposta risponde. Successivamente OP ha posto un'altra domanda (questa domanda) su come confrontare due personaggi che si assomigliano. Entrambe le domande avevano le migliori risposte e successivamente uno dei moderatori ha unito entrambe le domande selezionando la migliore risposta della seconda come migliore. Qualcuno ha modificato questa domanda, in modo che riassuma
Subin Jacob

In realtà, non ho aggiunto alcun contenuto dopo l'unione
Subin Jacob

24

EDIT Dopo l'unione di questa domanda con Come confrontare 'μ' e 'µ' in C #
Risposta originale pubblicata:

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

EDIT Dopo aver letto i commenti, sì, non è bene usare il metodo sopra perché potrebbe fornire risultati errati per altri tipi di input, per questo dovremmo usare normalize usando la decomposizione della piena compatibilità come menzionato nel wiki . (Grazie alla risposta pubblicata da 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();
    }

Produzione

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
 ________________________________________________________________

Durante la lettura delle informazioni in Unicode_equivalence ho trovato

La scelta dei criteri di equivalenza può influire sui risultati della ricerca. Ad esempio alcune legature tipografiche come U + FB03 (ffi), ..... quindi una ricerca di U + 0066 (f) come sottostringa riuscirebbe in una normalizzazione NFKC di U + FB03 ma non nella normalizzazione NFC di U + FB03.

Quindi per confrontare l'equivalenza dovremmo normalmente usare la FormKCnormalizzazione NFKC o la FormKDnormalizzazione NFKD.
Ero un po 'curioso di sapere di più su tutti i caratteri Unicode, quindi ho creato un campione che itererebbe su tutti i caratteri Unicode UTF-16e ho ottenuto alcuni risultati di cui voglio discutere

  • Informazioni sui personaggi i cui valori FormCe FormDnormalizzati non erano equivalenti
    Total: 12,118
    Character (int value): 192-197, 199-207, 209-214, 217-221, 224-253, ..... 44032-55203
  • Informazioni sui personaggi i cui valori FormKCe FormKDnormalizzati non erano equivalenti
    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
  • Tutto il cui carattere FormCe FormDnormalizzato il valore non fosse equivalente, ci FormKCe FormKDvalori normalizzati sono stati, inoltre, non equivale ad eccezione di questi personaggi
    Personaggi:901 '΅', 8129 '῁', 8141 '῍', 8142 '῎', 8143 '῏', 8157 '῝', 8158 '῞'
    , 8159 '῟', 8173 '῭', 8174 '΅'
  • Carattere extra il cui valore FormKCe FormKDnormalizzato non erano equivalenti, ma lì FormCe i FormDvalori normalizzati erano equivalenti
    Total: 119
    Caratteri: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'
  • Ci sono alcuni personaggi che non possono essere normalizzati , lanciano ArgumentExceptionse provati
    Total:2081 Characters(int value): 55296-57343, 64976-65007, 65534

Questi collegamenti possono essere davvero utili per capire quali regole governano l'equivalenza Unicode

  1. Unicode_equivalence
  2. Unicode_compatibility_characters

4
Strano ma funziona ... voglio dire sono due caratteri diversi con significati diversi e convertirli in superiori li rende uguali? Non vedo la logica ma bella soluzione +1
BudBrot

45
Questa soluzione maschera il problema e potrebbe causare problemi in un caso generale. Questo tipo di test lo troverebbe "m".ToUpper().Equals("µ".ToUpper());e sarebbe "M".ToUpper().Equals("µ".ToUpper());anche vero. Questo potrebbe non essere desiderabile.
Andrew Leach

6
-1 - questa è un'idea terribile. Non lavorare con Unicode in questo modo.
Konrad Rudolph

1
Invece di trucchi basati su ToUpper (), perché non utilizzare String.Equals ("μ", "μ", StringComparison.CurrentCultureIgnoreCase)?
svenv

6
C'è una buona ragione per distinguere tra "MICRO SIGN" e "GREEK SMALL LETTER MU" - per dire che "maiuscolo" di micro segno è ancora micro segno. Ma la capitalizzazione cambia micro in mega, felice ingegneria.
Greg

9

Molto probabilmente, ci sono due codici di caratteri diversi che creano (visibilmente) lo stesso carattere. Sebbene tecnicamente non siano uguali, sembrano uguali. Dai un'occhiata alla tabella dei caratteri e vedi se ci sono più istanze di quel personaggio. Oppure stampa il codice carattere dei due caratteri nel tuo codice.


6

Chiedi "come confrontarli" ma non ci dici cosa vuoi fare.

Esistono almeno due modi principali per confrontarli:

O li confronti direttamente come sei e sono diversi

Oppure si utilizza la normalizzazione della compatibilità Unicode se è necessario un confronto che trovi la corrispondenza.

Tuttavia, potrebbe esserci un problema perché la normalizzazione della compatibilità Unicode renderà molti altri caratteri uguali. Se vuoi che solo questi due caratteri siano trattati allo stesso modo, dovresti eseguire le tue funzioni di normalizzazione o confronto.

Per una soluzione più specifica abbiamo bisogno di conoscere il tuo problema specifico. Qual è il contesto in cui ti sei imbattuto in questo problema?


1
Il "micro segno" e il carattere minuscolo mu sono canonicamente equivalenti? L'uso della normalizzazione canonica ti darebbe un confronto più rigoroso.
Tanner Swett

@ TannerL.Swett: In realtà non sono nemmeno sicuro di come controllarlo dalla parte superiore della mia testa ...
hippietrail

1
In realtà, stavo importando un file con formula fisica. Hai ragione sulla normalizzazione. Devo affrontarlo più a fondo ..
DJ

Che tipo di file? Qualcosa di fatto a mano in semplice testo Unicode da una persona? O qualcosa prodotto da un'app in un formato specifico?
hippietrail

5

Se voglio essere pedante, direi che la tua domanda non ha senso, ma visto che ci avviciniamo al Natale e gli uccellini cantano, procederò con questo.

Prima di tutto, le 2 entità che stai cercando di confrontare sono glyphs, un glifo fa parte di un insieme di glifi fornito da quello che di solito è conosciuto come "font", la cosa che di solito viene fornita in a ttf, otfo qualunque formato di file tu sia utilizzando.

I glifi sono una rappresentazione di un dato simbolo e poiché sono una rappresentazione che dipende da un insieme specifico, non puoi semplicemente aspettarti di avere 2 simboli identici simili o addirittura "migliori", è una frase che non ha senso se consideri il contesto, dovresti almeno specificare quale carattere o insieme di glifi stai considerando quando formuli una domanda come questa.

Quello che di solito viene utilizzato per risolvere un problema simile a quello che stai riscontrando, è un OCR, essenzialmente un software che riconosce e confronta i glifi, Se C # fornisce un OCR per impostazione predefinita non lo so, ma generalmente è davvero pessimo idea se non hai davvero bisogno di un OCR e sai cosa farne.

Si può eventualmente finire per interpretare un libro di fisica come un antico libro greco senza menzionare il fatto che l'OCR è generalmente costoso in termini di risorse.

C'è una ragione per cui quei personaggi sono localizzati nel modo in cui sono localizzati, ma non farlo.


1

È possibile disegnare entrambi i caratteri con lo stesso stile e dimensione del carattere con DrawString metodo. Dopo che sono state generate due bitmap con simboli, è possibile confrontarle pixel per pixel.

Il vantaggio di questo metodo è che puoi confrontare non solo caratteri uguali assoluti, ma anche simili (con tolleranza definita).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.