Bagaimana cara membandingkan karakter Unicode yang "mirip"?

94

Saya jatuh ke dalam masalah yang mengejutkan.

Saya memuat file teks dalam aplikasi saya dan saya memiliki beberapa logika yang membandingkan nilai yang memiliki µ.

Dan saya menyadari bahwa meskipun teksnya sama, nilai pembandingnya salah.

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

Di baris berikutnya karakter µ disalin.

Namun, ini mungkin bukan satu-satunya karakter yang seperti ini.

Apakah ada cara di C # untuk membandingkan karakter yang terlihat sama tetapi sebenarnya berbeda?

DJ
sumber
159
Sepertinya Anda telah menemukan mu Schrödinger.
BoltClock
19
Mereka adalah karakter yang berbeda - meskipun terlihat sama, mereka memiliki kode karakter yang berbeda.
pengguna2864740
94
Selamat datang di Unicode.
ta.speot. Adalah
11
apa yang ingin kamu capai? bahwa keduanya harus sama bahkan kode karakter mereka berbeda tetapi wajah yang sama?
Jade
28
“Mirip” dan “terlihat sama” adalah konsep yang tidak jelas. Apakah yang mereka maksud adalah identitas mesin terbang, atau hanya kemiripan yang mirip? Betapa dekat? Perhatikan bahwa dua karakter mungkin memiliki mesin terbang yang identik di beberapa font, sangat mirip di font lain, dan sangat berbeda di font lain. Yang penting adalah mengapa Anda melakukan perbandingan seperti itu dan dalam konteks apa (dan penerimaan positif palsu dan negatif palsu).
Jukka K. Korpela

Jawaban:

125

Dalam banyak kasus, Anda dapat menormalkan kedua karakter Unicode ke bentuk normalisasi tertentu sebelum membandingkannya, dan keduanya harus bisa cocok. Tentu saja, bentuk normalisasi mana yang perlu Anda gunakan bergantung pada karakter itu sendiri; hanya karena mereka terlihat mirip tidak berarti mereka mewakili karakter yang sama. Anda juga perlu mempertimbangkan apakah itu sesuai untuk kasus penggunaan Anda - lihat komentar Jukka K. Korpela.

Untuk situasi khusus ini, jika Anda merujuk ke tautan dalam jawaban Tony , Anda akan melihat bahwa tabel untuk U + 00B5 mengatakan:

Dekomposisi <compat> GREEK SMALL LETTER MU (U + 03BC)

Ini berarti U + 00B5, karakter kedua dalam perbandingan awal Anda, dapat diuraikan menjadi U + 03BC, karakter pertama.

Jadi Anda akan menormalkan karakter menggunakan dekomposisi kompatibilitas penuh, dengan bentuk normalisasi KC atau KD. Inilah contoh singkat yang saya tulis untuk ditunjukkan:

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

Untuk detail tentang normalisasi Unicode dan bentuk normalisasi yang berbeda, lihat System.Text.NormalizationFormdan spesifikasi Unicode .

BoltClock
sumber
26
Terima kasih untuk tautan spesifikasi Unicode. Pertama kali saya membacanya. Catatan kecil darinya: "Bentuk Normalisasi KC dan KD tidak boleh diterapkan secara membabi buta ke sembarang teks .. Sebaiknya anggap Formulir Normalisasi ini seperti pemetaan huruf besar atau kecil: berguna dalam konteks tertentu untuk mengidentifikasi makna inti, tetapi juga melakukan modifikasi pada teks yang mungkin tidak selalu sesuai. "
pengguna2864740
149

Karena sangat berbeda simbolnya walaupun terlihat sama, yang pertama adalah huruf yang sebenarnya dan memiliki karakter code = 956 (0x3BC)dan yang kedua adalah tanda mikro dan memiliki 181 (0xB5).

Referensi:

Jadi jika Anda ingin membandingkannya dan Anda ingin keduanya sama, Anda perlu menanganinya secara manual, atau mengganti satu karakter dengan karakter lain sebelum dibandingkan. Atau gunakan kode berikut:

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

Dan Demo

Tony
sumber
11
Karena penasaran, apa alasan memiliki dua simbol µ? Anda tidak melihat K khusus dengan nama "tanda Kilo" (atau apakah Anda?).
MartinHaTh
12
@MartinHaTh: Menurut Wikipedia, ini "karena alasan sejarah" .
BoltClock
12
Unicode memiliki banyak karakter kompatibilitas yang dibawa dari kumpulan karakter yang lebih lama (seperti ISO 8859-1 ), untuk membuat konversi dari kumpulan karakter tersebut lebih mudah. Kembali ketika himpunan karakter dibatasi hingga 8 bit, mereka akan menyertakan beberapa mesin terbang (seperti beberapa huruf Yunani) untuk penggunaan matematika dan ilmiah yang paling umum. Penggunaan ulang mesin terbang berdasarkan penampilan adalah hal biasa, jadi tidak ada 'K' khusus yang ditambahkan. Tapi itu selalu menjadi solusi; simbol yang benar untuk "mikro" adalah huruf kecil Yunani sebenarnya mu, simbol yang benar untuk Ohm adalah huruf besar omega sebenarnya, dan seterusnya.
VGR
8
Tidak ada yang lebih baik daripada ketika sesuatu dilakukan untuk kismis histeris
paulm
11
Apakah ada K khusus untuk sereal?
86

Keduanya memiliki kode karakter yang berbeda: Lihat ini untuk detail selengkapnya

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

Dimana, yang pertama adalah:

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

Gambar

Vishal Suthar
sumber
39

Untuk contoh spesifik dari μ(mu) dan µ(tanda mikro), yang terakhir memiliki dekomposisi kompatibilitas dengan yang pertama, sehingga Anda dapat menormalkan string menjadi FormKCatau FormKDmengubah tanda mikro menjadi mus.

Namun, ada banyak set karakter yang mirip tetapi tidak setara dalam bentuk normalisasi Unicode. Misalnya, A(Latin), Α(Yunani), dan А(Sirilik). Situs web Unicode memiliki file confusables.txt dengan daftar ini, dimaksudkan untuk membantu pengembang menjaga dari serangan homograf . Jika perlu, Anda dapat mengurai file ini dan membuat tabel untuk "normalisasi visual" string.

dan04
sumber
Sangat baik untuk mengetahui saat menggunakan Normalisasi. Tampaknya mengejutkan bahwa mereka tetap berbeda.
pengguna2864740
4
@ user2864740: Jika tau bahasa Yunani huruf besar tidak tetap berbeda dari huruf T Romawi, akan sangat sulit untuk mengurutkan teks Yunani dan Romawi sesuai urutan abjad. Lebih lanjut, jika jenis huruf menggunakan gaya visual yang berbeda untuk huruf Yunani dan Romawi, akan sangat mengganggu jika huruf Yunani yang bentuknya menyerupai huruf Romawi dirender secara berbeda dari yang tidak.
supercat
7
Lebih penting lagi, menyatukan huruf Eropa akan membuat ToUpper/ ToLowersulit untuk diterapkan. Anda akan perlu untuk memiliki "B".ToLower()menjadi bdalam bahasa Inggris namun βdi Yunani dan вdi Rusia. Karena itu, hanya Turki (tanpa titik i) dan beberapa bahasa lain yang memerlukan aturan huruf besar yang berbeda dari default.
dan04
@ dan04: Saya ingin tahu apakah ada yang pernah mempertimbangkan untuk memberikan poin kode unik pada keempat variasi "i" dan "I" dalam bahasa Turki? Itu akan menghilangkan ambiguitas dalam perilaku toUpper / toLower.
supercat
34

Cari kedua karakter dalam database Unicode dan lihat perbedaannya .

Salah satunya adalah Huruf kecil Yunani µ dan yang lainnya adalah Tanda Mikro µ .

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
sumber
4
Bagaimana ini mendapatkan 37 suara positif? Itu tidak menjawab pertanyaan ("Bagaimana membandingkan karakter unicode"), itu hanya berkomentar mengapa contoh khusus ini tidak sama. Paling-paling, itu harus menjadi komentar atas pertanyaan itu. Saya memahami bahwa opsi pemformatan komentar tidak memungkinkan untuk mempostingnya sebaik opsi pemformatan jawaban, tetapi itu seharusnya bukan alasan yang valid untuk memposting sebagai jawaban.
Konerak
5
Sebenarnya pertanyaannya berbeda, menanyakan mengapa pemeriksaan persamaan μ dan µ kembali salah. Jawaban ini menjawabnya. Kemudian OP mengajukan pertanyaan lain (pertanyaan ini) bagaimana membandingkan dua karakter yang mirip. Kedua pertanyaan tersebut memiliki jawaban terbaik dan kemudian salah satu moderator menggabungkan kedua pertanyaan tersebut untuk memilih jawaban terbaik dari pertanyaan kedua sebagai terbaik. Seseorang mengedit pertanyaan ini, sehingga itu akan meringkas
Subin Jacob
Sebenarnya, saya tidak menambahkan konten apa pun setelah penggabungan
Subin Jacob
24

EDIT Setelah menggabungkan pertanyaan ini dengan Bagaimana membandingkan 'μ' dan 'µ' di C #
Jawaban asli diposting:

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

EDIT Setelah membaca komentar, ya tidak baik menggunakan metode di atas karena dapat memberikan hasil yang salah untuk beberapa jenis input lainnya, untuk ini kita harus menggunakan normalisasi menggunakan dekomposisi kompatibilitas penuh seperti yang disebutkan di wiki . (Berkat jawaban yang diposting oleh 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();
    }

Keluaran

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
 ________________________________________________________________

Saat membaca informasi di Unicode_equivalence saya temukan

Pilihan kriteria kesetaraan dapat mempengaruhi hasil pencarian. Misalnya beberapa pengikat tipografi seperti U + FB03 (ffi), ..... sehingga pencarian U + 0066 (f) sebagai substring akan berhasil dalam normalisasi NFKC dari U + FB03 tetapi tidak dalam normalisasi NFC dari U + FB03.

Jadi untuk membandingkan kesetaraan kita biasanya menggunakan FormKCnormalisasi yaitu NFKC atauFormKD normalisasi NFKD.
Saya sedikit penasaran untuk mengetahui lebih banyak tentang semua karakter Unicode jadi saya membuat sampel yang akan mengulang semua karakter Unicode di UTF-16dan saya mendapatkan beberapa hasil yang ingin saya diskusikan

  • Informasi tentang karakter yang nilainya FormCdan FormDnilai yang dinormalkan tidak setara
    Total: 12,118
    Character (int value): 192-197, 199-207, 209-214, 217-221, 224-253, ..... 44032-55203
  • Informasi tentang karakter yang nilainya FormKCdan FormKDnilai yang dinormalkan tidak setara
    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
  • Semua karakter yang FormCdan FormDnilainya dinormalisasi tidak setara, ada FormKCdan FormKDnilai yang dinormalisasi juga tidak setara kecuali karakter berikut.
    Karakter:901 '΅', 8129 '῁', 8141 '῍', 8142 '῎', 8143 '῏', 8157 '῝', 8158 '῞'
    , 8159 '῟', 8173 '῭', 8174 '΅'
  • Karakter ekstra yang FormKCdan FormKDnilainya dinormalisasi tidak setara, tetapi ada FormCdan FormDnilai yang dinormalkan setara
    Total: 119
    Karakter: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'
  • Ada beberapa karakter yang tidak dapat dinormalisasi , mereka melempar ArgumentExceptionjika dicoba
    Total:2081 Characters(int value): 55296-57343, 64976-65007, 65534

Tautan ini dapat sangat membantu untuk memahami aturan apa yang mengatur kesetaraan Unicode

  1. Unicode_equivalence
  2. Unicode_compatibility_characters
dbw
sumber
4
Aneh tapi berhasil ... Maksud saya mereka adalah dua karakter berbeda dengan arti berbeda dan mengubahnya menjadi lebih tinggi membuatnya sama? Saya tidak melihat logika tetapi solusi yang bagus +1
BudBrot
45
Solusi ini menutupi masalah, dan dapat menyebabkan masalah dalam kasus umum. Tes semacam ini akan menemukan itu "m".ToUpper().Equals("µ".ToUpper());dan "M".ToUpper().Equals("µ".ToUpper());juga benar. Ini mungkin tidak diinginkan.
Andrew Leach
6
-1 - ini adalah ide yang buruk. Jangan bekerja dengan Unicode seperti ini.
Konrad Rudolph
1
Alih-alih trik berbasis ToUpper (), mengapa tidak menggunakan String.Equals ("μ", "μ", StringComparison.CurrentCultureIgnoreCase)?
svenv
6
Ada satu alasan bagus untuk membedakan antara "TANDA MIKRO" dan "SURAT KECIL YUNANI MU" - untuk mengatakan bahwa "huruf besar" dari tanda mikro masih tanda mikro. Tapi kapitalisasi mengubah mikro menjadi mega, selamat rekayasa.
Greg
9

Kemungkinan besar, ada dua kode karakter berbeda yang membuat karakter (terlihat) sama. Meski secara teknis tidak sama, mereka terlihat setara. Lihat tabel karakter dan lihat apakah ada beberapa contoh karakter itu. Atau cetak kode karakter dari dua karakter di kode Anda.

PMF
sumber
6

Anda bertanya "bagaimana membandingkannya" tetapi Anda tidak memberi tahu kami apa yang ingin Anda lakukan.

Setidaknya ada dua cara utama untuk membandingkannya:

Baik Anda membandingkannya secara langsung apa adanya dan keduanya berbeda

Atau Anda menggunakan Unicode Compatibility Normalization jika Anda membutuhkan perbandingan yang menurut Anda cocok.

Mungkin ada masalah karena normalisasi kompatibilitas Unicode akan membuat banyak karakter lain sebanding. Jika Anda hanya ingin kedua karakter ini diperlakukan sama, Anda harus menggulung fungsi normalisasi atau perbandingan Anda sendiri.

Untuk solusi yang lebih spesifik, kami perlu mengetahui masalah spesifik Anda. Dalam konteks apa Anda menemukan masalah ini?

hippietrail
sumber
1
Apakah "tanda mikro" dan huruf kecil mu setara secara kanonik? Menggunakan normalisasi kanonik akan memberi Anda perbandingan yang lebih ketat.
Tanner Swett
@ TannerL.Swett: Sebenarnya saya bahkan tidak yakin bagaimana cara memeriksanya dari atas kepala saya ...
hippietrail
1
Sebenarnya, saya sedang mengimpor file dengan rumus fisika. Anda benar tentang normalisasi. Saya harus melewatinya lebih dalam ..
DJ
Jenis file apa? Sesuatu buatan tangan dalam teks Unicode biasa oleh seseorang? Atau sesuatu yang dihasilkan oleh aplikasi dalam format tertentu?
hippietrail
5

Jika saya ingin bertele-tele, saya akan mengatakan bahwa pertanyaan Anda tidak masuk akal, tetapi karena kita mendekati natal dan burung-burung bernyanyi, saya akan melanjutkan dengan ini.

Pertama, 2 entitas yang Anda coba bandingkan adalah glyphs, mesin terbang adalah bagian dari kumpulan mesin terbang yang disediakan oleh apa yang biasanya dikenal sebagai "font", hal yang biasanya ada di ttf,otf atau format file apa pun Anda. menggunakan.

Mesin terbang adalah representasi dari simbol tertentu, dan karena mereka adalah representasi yang bergantung pada himpunan tertentu, Anda tidak bisa hanya berharap untuk memiliki 2 simbol serupa atau bahkan identik "lebih baik", itu adalah frase yang tidak masuk akal jika Anda mempertimbangkan konteksnya, Anda setidaknya harus menentukan font atau kumpulan mesin terbang apa yang Anda pertimbangkan ketika Anda merumuskan pertanyaan seperti ini.

Apa yang biasanya digunakan untuk memecahkan masalah yang mirip dengan yang Anda hadapi, ini adalah OCR, pada dasarnya perangkat lunak yang mengenali dan membandingkan mesin terbang, Jika C # menyediakan OCR secara default, saya tidak tahu itu, tetapi umumnya sangat buruk ide jika Anda tidak benar-benar membutuhkan OCR dan Anda tahu apa yang harus dilakukan dengannya.

Anda mungkin dapat menafsirkan buku fisika sebagai buku Yunani kuno tanpa menyebutkan fakta bahwa OCR umumnya mahal dalam hal sumber daya.

Ada alasan mengapa karakter-karakter itu dilokalkan dengan cara dilokalkan, jangan lakukan itu.

pengguna2485710
sumber
1

Anda dapat menggambar kedua karakter dengan gaya dan ukuran font yang sama dengan DrawStringmetode. Setelah dua bitmap dengan simbol dibuat, dimungkinkan untuk membandingkannya piksel demi piksel.

Keuntungan dari metode ini adalah Anda tidak hanya dapat membandingkan karakter yang sama secara absolut, tetapi juga serupa (dengan toleransi yang pasti).

Ivan Kochurkin
sumber