Saya memiliki persyaratan yang relatif tidak jelas, tetapi rasanya harus memungkinkan menggunakan BCL.
Untuk konteks, saya mengurai string tanggal / waktu dalam Waktu Noda . Saya mempertahankan kursor logis untuk posisi saya dalam string input. Jadi, sementara string lengkapnya mungkin "3 Januari 2013" kursor logisnya mungkin berada di 'J'.
Sekarang, saya perlu mengurai nama bulan, membandingkannya dengan semua nama bulan yang diketahui untuk budaya:
- Peka budaya
- Tidak peka kasus
- Hanya dari titik kursor (bukan nanti; saya ingin melihat apakah kursor "melihat" nama bulan kandidat)
- Segera
- ... dan saya perlu tahu setelah itu berapa banyak karakter yang digunakan
The kode saat untuk melakukan hal ini umumnya bekerja, menggunakan CompareInfo.Compare
. Ini efektif seperti ini (hanya untuk bagian yang cocok - ada lebih banyak kode di hal yang nyata, tetapi tidak relevan dengan kecocokan):
internal bool MatchCaseInsensitive(string candidate, CompareInfo compareInfo)
{
return compareInfo.Compare(text, position, candidate.Length,
candidate, 0, candidate.Length,
CompareOptions.IgnoreCase) == 0;
}
Namun, itu bergantung pada kandidat dan wilayah yang kami bandingkan memiliki panjang yang sama. Baik-baik saja di sebagian besar waktu, tetapi tidak baik dalam beberapa kasus khusus. Misalkan kita memiliki sesuatu seperti:
// U+00E9 is a single code point for e-acute
var text = "x b\u00e9d y";
int position = 2;
// e followed by U+0301 still means e-acute, but from two code points
var candidate = "be\u0301d";
Sekarang perbandingan saya akan gagal. Saya bisa menggunakan IsPrefix
:
if (compareInfo.IsPrefix(text.Substring(position), candidate,
CompareOptions.IgnoreCase))
tapi:
- Itu mengharuskan saya membuat substring, yang sebaiknya saya hindari. (Saya melihat Noda Time secara efektif sebagai pustaka sistem; kinerja parsing mungkin penting untuk beberapa klien.)
- Itu tidak memberi tahu saya seberapa jauh untuk memajukan kursor sesudahnya
Pada kenyataannya, saya sangat curiga ini tidak akan sering muncul ... tetapi saya benar - benar ingin melakukan hal yang benar di sini. Saya juga sangat ingin dapat melakukannya tanpa menjadi ahli Unicode atau menerapkannya sendiri :)
(Dibesarkan sebagai bug 210 di Noda Time, jika ada yang ingin mengikuti kesimpulan akhirnya.)
Saya suka ide normalisasi. Saya perlu memeriksanya secara rinci untuk a) kebenaran dan b) kinerja. Dengan asumsi saya dapat membuatnya berfungsi dengan benar, saya masih tidak yakin bagaimana apakah itu layak untuk diubah secara keseluruhan - ini adalah jenis hal yang mungkin tidak akan pernah benar-benar muncul dalam kehidupan nyata, tetapi dapat merusak kinerja semua pengguna saya: (
Saya juga telah memeriksa BCL - yang tampaknya tidak menangani ini dengan baik. Kode sampel:
using System;
using System.Globalization;
class Test
{
static void Main()
{
var culture = (CultureInfo) CultureInfo.InvariantCulture.Clone();
var months = culture.DateTimeFormat.AbbreviatedMonthNames;
months[10] = "be\u0301d";
culture.DateTimeFormat.AbbreviatedMonthNames = months;
var text = "25 b\u00e9d 2013";
var pattern = "dd MMM yyyy";
DateTime result;
if (DateTime.TryParseExact(text, pattern, culture,
DateTimeStyles.None, out result))
{
Console.WriteLine("Parsed! Result={0}", result);
}
else
{
Console.WriteLine("Didn't parse");
}
}
}
Mengubah nama bulan khusus menjadi hanya "tempat tidur" dengan nilai teks "bEd" akan menguraikan dengan baik.
Oke, beberapa poin data lagi:
Biaya penggunaan
Substring
danIsPrefix
signifikan tetapi tidak mengerikan. Pada contoh "Jumat 12 April 2013 20:28:42" di laptop pengembangan saya, itu mengubah jumlah operasi parse yang dapat saya jalankan dalam sedetik dari sekitar 460K menjadi sekitar 400K. Saya lebih suka menghindari perlambatan itu jika memungkinkan, tetapi itu tidak terlalu buruk.Normalisasi kurang layak dari yang saya kira - karena tidak tersedia di Perpustakaan Kelas Portabel. Saya berpotensi menggunakannya hanya untuk build non-PCL, memungkinkan build PCL menjadi sedikit kurang tepat. Hit kinerja pengujian untuk normalisasi (
string.IsNormalized
) menurunkan kinerja menjadi sekitar 445K panggilan per detik, yang dapat saya tangani. Saya masih tidak yakin itu melakukan semua yang saya butuhkan - misalnya, nama bulan yang mengandung "ß" harus cocok dengan "ss" di banyak budaya, saya percaya ... dan normalisasi tidak melakukan itu.
text
tidak terlalu lama, Anda bisa melakukannyaif (compareInfo.IndexOf(text, candidate, position, options) == position)
. msdn.microsoft.com/en-us/library/ms143031.aspx Tapi jikatext
sangat lama itu akan membuang banyak waktu untuk mencari di luar yang dibutuhkan.String
kelas sama sekali dalam contoh ini dan gunakan secaraChar[]
langsung. Anda akhirnya akan menulis lebih banyak kode, tetapi itulah yang terjadi ketika Anda menginginkan kinerja tinggi ... atau mungkin Anda harus memprogram dalam C ++ / CLI ;-)Jawaban:
Saya akan mempertimbangkan masalah dari banyak <-> satu / banyak kasus yang muncul terlebih dahulu dan secara terpisah dari penanganan bentuk Normalisasi yang berbeda.
Sebagai contoh:
Cocok
heisse
tetapi kemudian terlalu banyak menggerakkan kursor 1. Dan:Cocok,
heiße
tetapi kemudian menggerakkan kursor 1 terlalu sedikit.Ini akan berlaku untuk karakter apa pun yang tidak memiliki pemetaan satu-ke-satu yang sederhana.
Anda perlu mengetahui panjang substring yang benar-benar cocok. Tapi
Compare
,IndexOf
.. dll membuang informasi itu. Ini bisa dimungkinkan dengan ekspresi reguler tetapi implementasinya tidak melakukan pelipatan huruf besar-kecil dan karenanya tidak cocokß
denganss/SS
mode tidak peka huruf besar kecil.Compare
dan.IndexOf
memang demikian. Dan mungkin akan mahal untuk membuat regex baru untuk setiap kandidat.Solusi paling sederhana untuk ini adalah dengan hanya menyimpan string secara internal jika bentuk terlipat case dan melakukan perbandingan biner dengan kandidat yang melipat case. Kemudian Anda dapat menggerakkan kursor dengan benar hanya
.Length
karena kursor tersebut untuk representasi internal. Anda juga mendapatkan sebagian besar kinerja yang hilang karena tidak harus menggunakanCompareOptions.IgnoreCase
.Sayangnya tidak ada kasus fungsi kali lipat built-in dan orang miskin kasus lipat tidak bekerja baik karena tidak ada pemetaan kasus penuh - yang
ToUpper
metode tidak berubahß
menjadiSS
.Misalnya ini berfungsi di Java (dan bahkan di Javascript), diberikan string yang ada dalam Bentuk Normal C:
Menyenangkan untuk dicatat bahwa perbandingan kasus abaikan Java tidak melakukan lipatan kasus penuh seperti C #
CompareOptions.IgnoreCase
. Jadi mereka berlawanan dalam hal ini: Java melakukan pemetaan kasus penuh, tetapi pelipatan casing sederhana - C # melakukan pemetaan kasus sederhana, tetapi pelipatan casing penuh.Jadi kemungkinan Anda memerlukan perpustakaan pihak ke-3 untuk melipat case string Anda sebelum menggunakannya.
Sebelum melakukan apa pun, Anda harus memastikan bahwa string Anda dalam bentuk normal C. Anda dapat menggunakan pemeriksaan cepat pendahuluan ini yang dioptimalkan untuk skrip Latin:
Ini memberikan positif palsu tetapi bukan negatif palsu, saya tidak berharap itu memperlambat 460k parses / s sama sekali saat menggunakan karakter skrip Latin meskipun itu perlu dilakukan pada setiap string. Dengan positif palsu Anda akan menggunakan
IsNormalized
untuk mendapatkan negatif benar / positif dan hanya setelah itu normalisasi jika perlu.Jadi kesimpulannya, prosesnya adalah memastikan bentuk C normal dulu, lalu case fold. Lakukan perbandingan biner dengan string yang diproses dan gerakkan kursor saat Anda memindahkannya saat ini.
sumber
æ
adalah sama denganae
, danffi
sama denganffi
. Normalisasi-C tidak menangani ligatur sama sekali, karena hanya mengizinkan pemetaan kompatibilitas (yang biasanya dibatasi untuk menggabungkan karakter).ffi
, tapi melewatkan yang lain, sepertiæ
. Masalah ini diperburuk oleh perbedaan antar budaya -æ
sama dengan diae
bawah en-US, tetapi tidak di bawah da-DK, seperti yang dibahas dalam dokumentasi MSDN untuk string . Dengan demikian, normalisasi (dalam bentuk apapun) dan pemetaan kasus bukanlah solusi yang memadai untuk masalah ini.Lihat apakah ini memenuhi persyaratan ..:
compareInfo.Compare
hanya tampil sekalisource
dimulai denganprefix
; jika tidak, makaIsPrefix
kembali-1
; jika tidak, panjang karakter yang digunakan dalamsource
.Namun, saya tidak tahu kecuali selisih
length2
oleh1
dengan kasus berikut:update :
Saya mencoba meningkatkan sedikit kinerja, tetapi tidak terbukti apakah ada bug dalam kode berikut:
Saya menguji dengan kasus tertentu, dan perbandingannya turun menjadi sekitar 3.
sumber
IndexOf
operasi awal harus melihat seluruh string dari posisi awal, yang akan menjadi masalah kinerja jika string input panjang.Ini sebenarnya mungkin tanpa normalisasi dan tanpa menggunakan
IsPrefix
.Kita perlu membandingkan jumlah elemen teks yang sama dengan jumlah karakter yang sama, tetapi tetap mengembalikan jumlah karakter yang cocok.
Saya telah membuat salinan
MatchCaseInsensitive
metode dari ValueCursor.cs di Noda Time dan memodifikasinya sedikit sehingga dapat digunakan dalam konteks statis:(Hanya disertakan untuk referensi, itu adalah kode yang tidak dapat dibandingkan dengan benar seperti yang Anda ketahui)
Varian berikut dari metode tersebut menggunakan StringInfo.GetNextTextElement yang disediakan oleh framework. Idenya adalah untuk membandingkan elemen teks dengan elemen teks untuk menemukan kecocokan dan jika ditemukan mengembalikan jumlah sebenarnya dari karakter yang cocok dalam string sumber:
Metode itu berfungsi dengan baik setidaknya menurut kasus pengujian saya (yang pada dasarnya hanya menguji beberapa varian string yang Anda berikan:
"b\u00e9d"
dan"be\u0301d"
).Namun, metode GetNextTextElement membuat substring untuk setiap elemen teks sehingga implementasi ini membutuhkan banyak perbandingan substring - yang akan berdampak pada kinerja.
Jadi, saya membuat varian lain yang tidak menggunakan GetNextTextElement tetapi melompati karakter yang menggabungkan Unicode untuk menemukan panjang kecocokan aktual dalam karakter:
Metode itu menggunakan dua pembantu berikut:
Saya belum melakukan benchmarking, jadi saya tidak begitu tahu apakah metode yang lebih cepat sebenarnya lebih cepat. Saya juga belum melakukan pengujian tambahan.
Tapi ini harus menjawab pertanyaan Anda tentang bagaimana melakukan pencocokan substring sensitif budaya untuk string yang mungkin menyertakan karakter gabungan Unicode.
Ini adalah kasus uji yang saya gunakan:
Nilai tupelnya adalah:
Menjalankan pengujian tersebut pada tiga metode menghasilkan hasil ini:
Dua pengujian terakhir menguji kasus ketika string sumber lebih pendek dari string yang cocok. Dalam hal ini metode asli (waktu Noda) akan berhasil juga.
sumber