Comment comparer des caractères Unicode qui «se ressemblent»?

94

Je tombe dans un problème surprenant.

J'ai chargé un fichier texte dans mon application et j'ai une logique qui compare la valeur ayant µ.

Et j'ai réalisé que même si les textes sont identiques, la valeur de comparaison est fausse.

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

Dans la dernière ligne, le caractère µ est copié.

Cependant, ce ne sont peut-être pas les seuls personnages qui ressemblent à ça.

Existe-t-il un moyen en C # de comparer les caractères qui se ressemblent mais qui sont en fait différents?

DJ
la source
158
On dirait que vous avez trouvé le mu de Schrödinger.
BoltClock
19
Ce sont des caractères différents - même s'ils se ressemblent, ils ont des codes de caractères différents.
user2864740
93
Bienvenue dans Unicode.
ta.speot.is
11
Que veux-tu accomplir? que ces deux devraient être égaux alors même leur code de caractère est différent mais le même visage?
Jade
28
«Se ressemblent» et «se ressemblent» sont des concepts vagues. Signifient-ils l'identité des glyphes ou simplement une similitude proche? A quelle distance? Notez que deux caractères peuvent avoir des glyphes identiques dans certaines polices, très similaires dans une autre et assez différents dans une autre police. Ce qui compte, c'est pourquoi vous feriez une telle comparaison et dans quel contexte (et l'acceptabilité des faux positifs et des faux négatifs).
Jukka K. Korpela

Réponses:

125

Dans de nombreux cas, vous pouvez normaliser les deux caractères Unicode dans une certaine forme de normalisation avant de les comparer, et ils devraient pouvoir correspondre. Bien entendu, le formulaire de normalisation à utiliser dépend des caractères eux-mêmes; juste parce qu'ils se ressemblent ne signifie pas nécessairement qu'ils représentent le même personnage. Vous devez également déterminer si cela convient à votre cas d'utilisation - voir le commentaire de Jukka K. Korpela.

Pour cette situation particulière, si vous vous référez aux liens dans la réponse de Tony , vous verrez que le tableau pour U + 00B5 dit:

Décomposition <compat> LETTRE MINUSCULE GRECQUE MU (U + 03BC)

Cela signifie que U + 00B5, le deuxième caractère de votre comparaison d'origine, peut être décomposé en U + 03BC, le premier caractère.

Vous allez donc normaliser les caractères en utilisant une décomposition de compatibilité complète, avec les formes de normalisation KC ou KD. Voici un exemple rapide que j'ai écrit pour démontrer:

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

Pour plus de détails sur la normalisation Unicode et les différentes formes de normalisation se référer à System.Text.NormalizationFormet la spécification Unicode .

BoltClock
la source
26
Merci pour le lien de spécification Unicode. C'est la première fois que j'en ai lu. Petite remarque: "Les formulaires de normalisation KC et KD ne doivent pas être appliqués aveuglément à du texte arbitraire. Il est préférable de considérer ces formulaires de normalisation comme des mappages en majuscules ou minuscules: utiles dans certains contextes pour identifier les significations essentielles, mais des modifications du texte qui ne sont pas toujours appropriées. "
user2864740
149

Parce que ce sont des symboles vraiment différents même s'ils se ressemblent, le premier est la lettre réelle et a un caractère code = 956 (0x3BC)et le second est le micro signe et a 181 (0xB5).

Références:

Donc, si vous voulez les comparer et que vous avez besoin qu'ils soient égaux, vous devez le gérer manuellement ou remplacer un caractère par un autre avant la comparaison. Ou utilisez le code suivant:

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

Et la démo

Tony
la source
11
Par curiosité, quel est le raisonnement pour avoir deux symboles µ? Vous ne voyez pas de K dédié avec le nom "signe Kilo" (ou vous?).
MartinHaTh
12
@MartinHaTh: Selon Wikipedia, c'est "pour des raisons historiques" .
BoltClock
12
Unicode a beaucoup de caractères de compatibilité provenant d'anciens jeux de caractères (comme ISO 8859-1 ), pour faciliter la conversion de ces jeux de caractères. À l'époque où les jeux de caractères étaient limités à 8 bits, ils incluaient quelques glyphes (comme certaines lettres grecques) pour les utilisations mathématiques et scientifiques les plus courantes. La réutilisation des glyphes basée sur l'apparence était courante, donc aucun «K» spécialisé n'a été ajouté. Mais c'était toujours une solution de contournement; le symbole correct pour «micro» est le mu minuscule grec réel, le symbole correct pour Ohm est l'oméga majuscule réel, et ainsi de suite.
VGR
8
Rien de mieux que quand quelque chose est fait pour les raisins secs hystériques
paulm
11
Existe-t-il un K spécial pour les céréales?
86

Ils ont tous deux des codes de caractères différents: reportez-vous à ceci pour plus de détails

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

Où, le premier est:

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

Image

Vishal Suthar
la source
39

Pour l'exemple spécifique de μ(mu) et µ(micro signe), ce dernier a une décomposition de compatibilité avec le premier, vous pouvez donc normaliser la chaîne en FormKCou FormKDconvertir les micro signes en mus.

Cependant, il existe de nombreux jeux de caractères qui se ressemblent mais qui ne sont équivalents sous aucune forme de normalisation Unicode. Par exemple, A(latin), Α(grec) et А(cyrillique). Le site Web Unicode a un fichier confusables.txt avec une liste de ceux-ci, destiné à aider les développeurs à se prémunir contre les attaques homographes . Si nécessaire, vous pouvez analyser ce fichier et créer un tableau pour la «normalisation visuelle» des chaînes.

dan04
la source
Vraiment bon à savoir lors de l'utilisation de Normaliser. Il semble surprenant qu'ils restent distincts.
user2864740
4
@ user2864740: Si une majuscule grecque tau ne restait pas distincte d'une lettre romaine T, il serait très difficile de classer judicieusement le texte grec et romain dans l'ordre alphabétique. De plus, si une police devait utiliser un style visuel différent pour les lettres grecques et romaines, il serait très distrayant si les lettres grecques dont les formes ressemblaient à des lettres romaines étaient rendues différemment de celles qui ne le faisaient pas.
supercat
7
Plus important encore, l'unification des alphabets européens rendrait ToUpper/ ToLowerdifficile à mettre en œuvre. Vous devrez "B".ToLower()être ben anglais mais βen grec et вen russe. Dans l'état actuel des choses, seul le turc (sans point i) et quelques autres langues ont besoin de règles de casse différentes de celles par défaut.
dan04
@ dan04: Je me demande si quelqu'un a déjà envisagé d'attribuer des points de code uniques aux quatre variantes du "i" et du "je" turcs? Cela aurait éliminé toute ambiguïté dans le comportement de toUpper / toLower.
supercat
34

Recherchez les deux caractères dans une base de données Unicode et voyez la différence .

L'un est la lettre minuscule grecque µ et l'autre est le micro signe µ .

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
la source
4
Comment cela a-t-il obtenu 37 votes positifs? Il ne répond pas à la question ("Comment comparer les caractères Unicode"), il commente simplement pourquoi cet exemple particulier n'est pas égal. Au mieux, cela devrait être un commentaire sur la question. Je comprends que les options de formatage des commentaires ne permettent pas de le publier aussi bien que les options de formatage des réponses, mais cela ne devrait pas être une raison valable de publier comme réponse.
Konerak
5
En fait, la question était différente, demandant pourquoi les contrôles d'égalité μ et µ retournent faux. Cette réponse y répond. Plus tard, OP a posé une autre question (cette question) comment comparer deux personnages qui se ressemblent. Les deux questions ont eu les meilleures réponses et plus tard l'un des modérateurs a fusionné les deux questions en sélectionnant la meilleure réponse de la seconde comme meilleure. Quelqu'un a édité cette question, afin qu'elle résume
Subin Jacob
En fait, je n'ai ajouté aucun contenu après la fusion
Subin Jacob
24

EDIT Après la fusion de cette question avec Comment comparer 'μ' et 'µ' en C #
La réponse originale publiée:

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

ÉDITER Après avoir lu les commentaires, oui, il n'est pas bon d'utiliser la méthode ci-dessus car elle peut fournir des résultats erronés pour certains autres types d'entrées, pour cela, nous devrions utiliser normaliser en utilisant la décomposition de compatibilité complète comme mentionné dans le wiki . (Merci à la réponse publiée par 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();
    }

Production

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
 ________________________________________________________________

En lisant les informations dans Unicode_equivalence, j'ai trouvé

Le choix des critères d'équivalence peut affecter les résultats de la recherche. Par exemple, certaines ligatures typographiques comme U + FB03 (ffi), ..... donc une recherche de U + 0066 (f) comme sous-chaîne réussirait une normalisation NFKC de U + FB03 mais pas une normalisation NFC de U + FB03.

Donc, pour comparer l'équivalence, nous devrions normalement utiliser FormKC normalisation NFKC ou la FormKDnormalisation NFKD.
J'étais peu curieux d'en savoir plus sur tous les caractères Unicode, j'ai donc créé un échantillon qui itérerait sur tout le caractère Unicode UTF-16et j'ai obtenu des résultats dont je veux discuter

  • Informations sur les personnages dont FormC et des FormDvaleurs normalisées ne sont pas équivalents
    Total: 12,118
    Character (int value): 192-197, 199-207, 209-214, 217-221, 224-253, ..... 44032-55203
  • Informations sur les personnages dont FormKC et des FormKDvaleurs normalisées ne sont pas équivalents
    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
  • Tout le personnage dont FormCFormD valeur normalisée et n'étaient pas équivalents, là FormKCet les FormKDvaleurs normalisées n'étaient pas non plus équivalentes sauf ces caractères
    Caractères:901 '΅', 8129 '῁', 8141 '῍', 8142 '῎', 8143 '῏', 8157 '῝', 8158 '῞'
    , 8159 '῟', 8173 '῭', 8174 '΅'
  • Caractère supplémentaire dont FormKC et FormKDvaleur normalisée ne sont pas équivalents, mais il FormCet FormDvaleurs normalisées étaient équivalentes
    Total: 119
    caractères: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'
  • Il y a des personnages qui ne peuvent pas être normalisés , ils lancentArgumentException s'ils sont essayés
    Total:2081 Characters(int value): 55296-57343, 64976-65007, 65534

Ces liens peuvent être vraiment utiles pour comprendre quelles règles régissent l'équivalence Unicode

  1. Unicode_equivalence
  2. Unicode_compatibility_characters
dbw
la source
4
Étrange mais fonctionne ... Je veux dire, ce sont deux caractères différents avec des significations différentes et les convertir en supérieur les rend égaux? Je ne vois pas la logique mais belle solution +1
BudBrot
45
Cette solution masque le problème et pourrait entraîner des problèmes dans un cas général. Ce genre de test trouverait cela "m".ToUpper().Equals("µ".ToUpper());et "M".ToUpper().Equals("µ".ToUpper());est également vrai. Cela peut ne pas être souhaitable.
Andrew Leach
6
-1 - c'est une idée terrible. Ne travaillez pas avec Unicode de cette manière.
Konrad Rudolph
1
Au lieu des astuces basées sur ToUpper (), pourquoi ne pas utiliser String.Equals ("μ", "μ", StringComparison.CurrentCultureIgnoreCase)?
svenv
6
Il y a une bonne raison de faire la distinction entre «MICRO SIGN» et «GREEK SMALL LETTER MU» - dire que «majuscule» du micro-signe est toujours un micro-signe. Mais la capitalisation change du micro au méga, bonne ingénierie.
Greg
9

Très probablement, il existe deux codes de caractères différents qui font (visiblement) le même caractère. Bien que techniquement non égaux, ils semblent égaux. Jetez un œil à la table des caractères et voyez s'il existe plusieurs instances de ce caractère. Ou imprimez le code de caractère des deux caractères de votre code.

PMF
la source
6

Vous demandez "comment les comparer" mais vous ne nous dites pas ce que vous voulez faire.

Il existe au moins deux façons principales de les comparer:

Soit vous les comparez directement tels que vous êtes et ils sont différents

Ou vous utilisez la normalisation de compatibilité Unicode si vous avez besoin d'une comparaison qui les trouve correspondre.

Il pourrait y avoir un problème car la normalisation de la compatibilité Unicode rendra de nombreux autres caractères égaux. Si vous voulez que seuls ces deux caractères soient traités de la même manière, vous devez lancer vos propres fonctions de normalisation ou de comparaison.

Pour une solution plus spécifique, nous devons connaître votre problème spécifique. Quel est le contexte dans lequel vous êtes tombé sur ce problème?

hippietrail
la source
1
Le "micro signe" et le caractère mu minuscule sont-ils canoniquement équivalents? L'utilisation de la normalisation canonique vous donnerait une comparaison plus stricte.
Tanner Swett
@ TannerL.Swett: En fait, je ne sais même pas comment vérifier cela du haut de ma tête ...
hippietrail
1
En fait, j'importais un fichier avec une formule physique. Vous avez raison sur la normalisation. Je dois le parcourir plus en profondeur ..
DJ
Quel genre de fichier? Quelque chose fait à la main en texte Unicode par une personne? Ou quelque chose de produit par une application dans un format spécifique?
hippietrail
5

Si je veux être pédant, je dirais que votre question n'a pas de sens, mais puisque nous approchons de Noël et que les oiseaux chantent, je vais continuer.

Tout d'abord, les 2 entités que vous essayez de comparer sont glyphs, un glyphe fait partie d'un ensemble de glyphes fournis par ce que l'on appelle généralement une "police", ce qui se présente généralement sous la forme d'un ttf,otf ou quel que soit le format de fichier que vous êtes en utilisant.

Les glyphes sont une représentation d'un symbole donné, et comme ils sont une représentation qui dépend d'un ensemble spécifique, vous ne pouvez pas vous attendre à avoir 2 symboles identiques ou même "meilleurs" identiques, c'est une phrase qui n'a pas de sens si vous considérez le contexte, vous devriez au moins spécifier la police ou le jeu de glyphes que vous envisagez lorsque vous formulez une question comme celle-ci.

Ce qui est généralement utilisé pour résoudre un problème similaire à celui que vous rencontrez, c'est un OCR, essentiellement un logiciel qui reconnaît et compare les glyphes, Si C # fournit un OCR par défaut, je ne le sais pas, mais c'est généralement un très mauvais idée si vous n'avez pas vraiment besoin d'un OCR et que vous savez quoi en faire.

Vous pouvez éventuellement interpréter un livre de physique comme un livre grec ancien sans mentionner le fait que l'OCR coûte généralement cher en termes de ressources.

Il y a une raison pour laquelle ces caractères sont localisés de la manière dont ils sont localisés, mais ne le faites pas.

user2485710
la source
1

Il est possible de dessiner les deux caractères avec le même style et la même taille de police avec DrawString méthode. Une fois que deux bitmaps avec symboles ont été générés, il est possible de les comparer pixel par pixel.

L'avantage de cette méthode est que vous pouvez comparer non seulement des caractères égaux absolus, mais également similaires (avec une tolérance définie).

Ivan Kochurkin
la source