Bir dizenin ortasından kültüre duyarlı bir "başlar" işlemini nasıl gerçekleştirebilirim?


106

Nispeten belirsiz bir gereksinimim var, ancak BCL kullanılarak mümkün olması gerektiği gibi geliyor .

Bağlam için, Noda Time'da bir tarih / saat dizesini ayrıştırıyorum . Giriş dizesi içindeki konumum için mantıksal bir imleç tutuyorum. Dolayısıyla, dizenin tamamı "3 Ocak 2013" olsa da, mantıksal imleç "J" konumunda olabilir.

Şimdi, ay adını, kültürün bilinen tüm ay adlarıyla karşılaştırarak ayrıştırmam gerekiyor:

  • Kültüre duyarlı
  • Büyük / küçük harf duyarsız
  • Sadece imlecin bulunduğu noktadan (daha sonra değil; imlecin aday ay adına "bakıp bakmadığını" görmek istiyorum)
  • Hızlı bir şekilde
  • ... ve daha sonra kaç karakterin kullanıldığını bilmem gerekiyor

Bunu yapmak için mevcut kod genellikle CompareInfo.Compare. Etkili bir şekilde şöyle (sadece eşleşen kısım için - gerçek şeyde daha fazla kod var, ancak maçla alakalı değil):

internal bool MatchCaseInsensitive(string candidate, CompareInfo compareInfo)
{
    return compareInfo.Compare(text, position, candidate.Length,
                               candidate, 0, candidate.Length, 
                               CompareOptions.IgnoreCase) == 0;
}

Ancak bu, adaya ve karşılaştırdığımız bölgenin aynı uzunlukta olmasına bağlıdır. Çoğu zaman iyidir , ancak bazı özel durumlarda iyi değildir . Şöyle bir şeye sahip olduğumuzu varsayalım:

// 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";

Şimdi karşılaştırmam başarısız olacak. Kullanabilirim IsPrefix:

if (compareInfo.IsPrefix(text.Substring(position), candidate,
                         CompareOptions.IgnoreCase))

fakat:

  • Bu, gerçekten kaçınmayı tercih ettiğim bir alt dize oluşturmamı gerektiriyor. (Noda Time'ı etkili bir sistem kitaplığı olarak görüyorum; performansın ayrıştırılması bazı istemciler için önemli olabilir.)
  • Daha sonra imleci ne kadar ilerleteceğimi söylemiyor

Gerçekte, şiddetle bu çok sık gelip olmaz sanıyorum ... ama gerçekten istiyorum gibi burada doğru olanı yapmak için. Ayrıca bunu bir Unicode uzmanı olmadan ya da kendim uygulamadan da yapabilmeyi gerçekten çok isterim :)

( Herhangi birinin nihai bir sonucu takip etmek istemesi durumunda, Noda Time'da hata 210 olarak yükseltildi .)

Normalleşme fikrini seviyorum. Bunu a) doğruluk ve b) performans açısından ayrıntılı olarak kontrol etmem gerekiyor. Doğru çalışmasını sağlayabileceğimi varsayarsak , yine de her şeyi değiştirmeye değip değmeyeceğinden emin değilim - bu muhtemelen gerçek hayatta asla ortaya çıkmayacak , ancak tüm kullanıcılarımın performansına zarar verebilecek türden bir şey : (

BCL'yi de kontrol ettim - bu da bunu doğru bir şekilde ele almıyor gibi görünüyor. Basit kod:

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

Özel ay adını "bEd" metin değerine sahip "bed" olarak değiştirmek, iyi ayrıştırır.

Tamam, birkaç veri noktası daha:

  • Kullanma maliyeti Substringve IsPrefixönemli ancak korkunç değildir. Geliştirme dizüstü bilgisayarımdaki "12 Nisan 2013 Cuma 20:28:42" örneğinde, bir saniyede gerçekleştirebileceğim ayrıştırma işlemlerinin sayısını yaklaşık 460K'dan yaklaşık 400K'ya değiştiriyor. Mümkünse bu yavaşlamadan kaçınmayı tercih ederim, ama çok da kötü değil .

  • Normalleştirme düşündüğümden daha az uygulanabilir - çünkü Taşınabilir Sınıf Kitaplıklarında mevcut değil. Potansiyel olarak sadece PCL olmayan yapılar için kullanabilirim, bu da PCL yapılarının biraz daha az doğru olmasına izin verir. Normalleştirme ( string.IsNormalized) testinin performans artışı, performansı saniyede yaklaşık 445K çağrıya düşürüyor ve bununla yaşayabiliyorum. Hâlâ ihtiyacım olan her şeyi yaptığından emin değilim - örneğin, "ß" içeren bir ay adının birçok kültürde "ss" ile eşleşmesi gerektiğine inanıyorum ... ve normalleştirmenin bunu yapmadığını düşünüyorum.


Alt dize oluşturmanın performans artışından kaçınma arzunuzu anlasam da, bunu yapmak en iyisi olabilir, ancak oyunun başlarında her şeyi İLK olarak seçilen bir unicode normalleştirme biçimine kaydırıp ardından "nokta nokta yürüyebileceğinizi bilerek" ". Muhtemelen D-formu.
IDisposable

@IDisposable: Evet, bunu merak ettim. Açıkçası önceden ay isimlerini normalleştirebilirim. En azından normalleştirmeyi sadece bir kez yapabilirim. Normalleştirme prosedürünün önce bir şey yapılması gerekip gerekmediğini kontrol edip etmediğini merak ediyorum. Normalleştirme konusunda pek tecrübem yok - kesinlikle bakmam gereken bir yol.
Jon Skeet

1
Eğer senin textçok uzun değil, yapabileceğin if (compareInfo.IndexOf(text, candidate, position, options) == position). msdn.microsoft.com/en-us/library/ms143031.aspx Ama eğer textçok uzunsa bu, ihtiyaç duyduğu yerin ötesinde arama yapmak için çok zaman kaybedecektir.
Jim Mischel

1
Sadece kullanarak baypas Stringsınıfını hiç bu örnekte ve kullanma Char[]doğrudan. Daha fazla kod yazacaksınız, ama yüksek performans istediğinizde olan budur ... veya belki de C ++ / CLI ;-) ile programlama yapmanız gerekir
intrepidis

1
Will CompareOptions.IgnoreNonSpace automagicallylar sizin için bu bakamaz? Bu bir (olabilir Sanki bana (docco dan değil, bu iPad üzgün! Dan testine bir konumda) bakar o seçeneğin?) Kullanım-case. " Dize karşılaştırmasının, aksan gibi karakterlerin aralıksız kombinasyonunu yok sayması gerektiğini belirtir. "
2013

Yanıtlar:


41

Birçok <-> bir / birçok durum eşleştirmesi sorununu ilk olarak ve farklı Normalleştirme biçimlerini ele almaktan ayrı olarak ele alacağım.

Örneğin:

x heiße y
  ^--- cursor

Eşleşiyor heisseama sonra imleci 1 çok fazla hareket ettiriyor. Ve:

x heisse y
  ^--- cursor

Eşleşir heißeancak ardından imleç 1'i çok daha az hareket ettirir.

Bu, basit bire bir eşlemesi olmayan tüm karakterler için geçerli olacaktır.

Gerçekte eşleşen alt dizenin uzunluğunu bilmeniz gerekir. Ama Compare, IndexOf..etc bu bilgiyi bir kenara atın. Düzenli ifadeler ile mümkün olabilir ama uygulama tam vaka katlama yapmaz ve böylece uymuyor ßiçin ss/SSolsa bile harf duyarsız modda .Compareve .IndexOfyapmak. Ve yine de her aday için yeni normal ifadeler oluşturmak muhtemelen maliyetli olacaktır.

Bunun en basit çözümü, dizeleri büyük / küçük harf katlanmış biçimde dahili olarak depolamak ve büyük / küçük harf katlamalı adaylarla ikili karşılaştırmalar yapmaktır. Ardından .Length, imleç dahili gösterim için olduğundan imleci doğru şekilde hareket ettirebilirsiniz . Ayrıca, kaybedilen performansın çoğunu kullanmak zorunda kalmadan geri alırsınız CompareOptions.IgnoreCase.

- Ne yazık ki yerleşik hiçbir örnek olayın tamamını haritalama olduğundan yoksul adamın vaka katlama ya çalışmaz ve hiçbir durumda katlama fonksiyonu yoktur ToUpperyöntem açılmıyor ßiçine SS.

Örneğin bu, Normal Form C'deki dize verildiğinde Java'da (ve hatta Javascript'te) çalışır:

//Poor man's case folding.
//There are some edge cases where this doesn't work
public static String toCaseFold( String input, Locale cultureInfo ) {
    return input.toUpperCase(cultureInfo).toLowerCase(cultureInfo);
}

Java'nın büyük / küçük harf görmezden gelen karşılaştırmasının, C # 'ler gibi tam büyük / küçük harf katlama yapmadığını not etmek eğlenceli CompareOptions.IgnoreCase. Dolayısıyla, bu açıdan zıttırlar: Java tam harf eşleme yapar, ancak basit büyük / küçük harf katlama - C # basit harf eşleme yapar, ancak tam büyük harf katlama yapar.

Bu nedenle, dizelerinizi kullanmadan önce katlamak için bir 3. parti kitaplığa ihtiyacınız olabilir.


Herhangi bir şey yapmadan önce dizelerinizin normal C formunda olduğundan emin olmalısınız. Latin alfabesi için optimize edilmiş bu hızlı ön kontrolü kullanabilirsiniz:

public static bool MaybeRequiresNormalizationToFormC(string input)
{
    if( input == null ) throw new ArgumentNullException("input");

    int len = input.Length;
    for (int i = 0; i < len; ++i)
    {
        if (input[i] > 0x2FF)
        {
            return true;
        }
    }

    return false;
}

Bu, yanlış pozitifler verir, ancak yanlış negatifler vermez, Latin alfabesi karakterlerini kullanırken, her dizede gerçekleştirilmesi gerekmesine rağmen 460k ayrıştırmayı / s'yi hiç yavaşlatmasını beklemiyorum. Yanlış bir pozitif ile IsNormalizedgerçek bir negatif / pozitif elde etmek için kullanırsınız ve ancak bundan sonra gerekirse normalleşirsiniz.


Sonuç olarak, işlem önce normal C formunu, ardından kasa katlamayı sağlamaktır. İşlenen dizelerle ikili karşılaştırmalar yapın ve imleci şu anda hareket ettirirken hareket ettirin.


Bunun için teşekkürler - normalleştirme formu C'yi daha ayrıntılı incelemem gerekecek, ancak bunlar harika ipuçları. Sanırım "PCL altında pek doğru çalışmıyor" (normalleştirme sağlamaz) ile yaşayabilirim. Büyük / küçük harf ayırma için bir 3. taraf kitaplığı kullanmak burada aşırıya kaçacaktır - şu anda 3. taraf bağımlılığımız yok ve sadece BCL'nin bile başa çıkmadığı bir köşe vakası için bir tane sunmak bir acı olacaktır. Büyük / küçük harf ayırma kültüre duyarlıdır, btw (örneğin Türkçe)?
Jon Skeet

2
@JonSkeet evet, Türkçede alt kat haritalamalarında kendi modu hak ediyor: P CaseFolding.txt
Esailija

Bu cevabın temel bir kusuru var gibi görünüyor, çünkü karakterlerin bitişik harflerle (ve tam tersi) yalnızca büyük / küçük harf katlama sırasında eşleştiğini ima ediyor. Olay bu değil; büyük / küçük harfe bakılmaksızın karakterlere eşit kabul edilen bitişik harfler vardır. Örneğin, en-US kültürü altında æ, eşittir aeve eşittir ffi. C-normalizasyonu bitişik harfleri hiç işlemez, çünkü yalnızca uyumluluk eşlemelerine izin verir (tipik olarak karakterleri birleştirmekle sınırlıdır).
Douglas

KC- ve KD-normalizasyonu, gibi bazı bitişik harfleri işler , ancak gibi diğerlerini kaçırır æ. Sorun, kültürler arasındaki farklılıklar nedeniyle daha da kötüleşiyor - dizeler için MSDN belgelerinde tartışıldığı gibi , en- æUS'ye eşit ae, ancak da-DK kapsamında değil . Bu nedenle, normalleştirme (herhangi bir biçimde) ve durum haritalama bu sorun için yeterli bir çözüm değildir.
Douglas

Önceki yorumumda küçük bir düzeltme: C-normalizasyonu , uyumluluk eşlemelerine (ligatürler gibi) değil, yalnızca kanonik eşlemelere (karakterleri birleştirmek için olduğu gibi) izin verir .
Douglas

21

Bunun gereksinimi karşılayıp karşılamadığını görün ..:

public static partial class GlobalizationExtensions {
    public static int IsPrefix(
        this CompareInfo compareInfo,
        String source, String prefix, int startIndex, CompareOptions options
        ) {
        if(compareInfo.IndexOf(source, prefix, startIndex, options)!=startIndex)
            return ~0;
        else
            // source is started with prefix
            // therefore the loop must exit
            for(int length2=0, length1=prefix.Length; ; )
                if(0==compareInfo.Compare(
                        prefix, 0, length1, 
                        source, startIndex, ++length2, options))
                    return length2;
    }
}

compareInfo.Compareyalnızca sourcebaşladıktan sonra performans gösterir prefix; değilse, IsPrefixgeri döner -1; aksi takdirde, kullanılan karakterlerin uzunluğu source.

Ancak, ben artım dışında hiçbir fikrim yok length2tarafından 1aşağıdaki dava ile:

var candidate="ßssß\u00E9\u0302";
var text="abcd ssßss\u0065\u0301\u0302sss";

var count=
    culture.CompareInfo.IsPrefix(text, candidate, 5, CompareOptions.IgnoreCase);

güncelleme :

Biraz performans geliştirmeye çalıştım, ancak aşağıdaki kodda hata olup olmadığı kanıtlanmadı:

public static partial class GlobalizationExtensions {
    public static int Compare(
        this CompareInfo compareInfo,
        String source, String prefix, int startIndex, ref int length2, 
        CompareOptions options) {
        int length1=prefix.Length, v2, v1;

        if(0==(v1=compareInfo.Compare(
            prefix, 0, length1, source, startIndex, length2, options))
            ) {
            return 0;
        }
        else {
            if(0==(v2=compareInfo.Compare(
                prefix, 0, length1, source, startIndex, 1+length2, options))
                ) {
                ++length2;
                return 0;
            }
            else {
                if(v1<0||v2<0) {
                    length2-=2;
                    return -1;
                }
                else {
                    length2+=2;
                    return 1;
                }
            }
        }
    }

    public static int IsPrefix(
        this CompareInfo compareInfo,
        String source, String prefix, int startIndex, CompareOptions options
        ) {
        if(compareInfo.IndexOf(source, prefix, startIndex, options)
                !=startIndex)
            return ~0;
        else
            for(int length2=
                    Math.Min(prefix.Length, source.Length-(1+startIndex)); ; )
                if(0==compareInfo.Compare(
                        source, prefix, startIndex, ref length2, options))
                    return length2;
    }
}

Özel durumla test ettim ve karşılaştırmayı yaklaşık 3'e düşürdüm.


Gerçekten böyle döngü yapmak zorunda kalmamayı tercih ederim . Kuşkusuz, erken çıkışla, yalnızca bir şey bulunursa döngü yapması gerekecek, ancak yine de örneğin "Şubat" ile eşleşmek için 8 dizi karşılaştırması yapmak zorunda kalmamayı tercih ederim. Daha iyi bir yolu olmalıymış gibi geliyor. Ayrıca, ilk IndexOfişlemin başlangıç ​​konumundan tüm dizgiye bakması gerekir; bu, giriş dizesi uzunsa performans açısından bir sıkıntı olur.
Jon Skeet

@JonSkeet: Teşekkürler. Döngünün azaltılıp azaltılamayacağını algılamak için belki bir şeyler eklenebilir. Bunu düşüneceğim.
Ken Kin

@JonSkeet: Yansıma kullanmayı düşünür müsünüz? Yöntemlerin izini sürdüğümden beri, çok uzak olmayan yerel yöntemleri çağırmaya düşüyorlar.
Ken Kin

3
Aslında. Noda Time, Unicode ayrıntıları işine girmek istemiyor :)
Jon Skeet

2
Benzer bir sorunu bir kez çözdüm (HTML'de arama dizesi vurgulama). Ben de benzer şekilde yaptım. Döngüyü ve arama stratejisini, önce olası vakaları kontrol ederek çok hızlı bir şekilde tamamlanmasını sağlayacak şekilde ayarlayabilirsiniz. Bununla ilgili güzel olan şey, tamamen doğru görünmesi ve hiçbir Unicode detayının kodunuza sızmamasıdır.
usr

9

Bu aslında normalleştirme olmadan ve kullanmadan mümkündür IsPrefix.

Aynı sayıda karakterle aynı sayıda metin öğesini karşılaştırmamız gerekir , ancak yine de eşleşen karakter sayısını döndürmemiz gerekir.

Noda Time'da ValueCursor.cs'denMatchCaseInsensitive yöntemin bir kopyasını oluşturdum ve statik bir bağlamda kullanılabilmesi için biraz değiştirdim:

// Noda time code from MatchCaseInsensitive in ValueCursor.cs
static int IsMatch_Original(string source, int index, string match, CompareInfo compareInfo)
{
    unchecked
    {
        if (match.Length > source.Length - index)
        {
            return 0;
        }

        // TODO(V1.2): This will fail if the length in the input string is different to the length in the
        // match string for culture-specific reasons. It's not clear how to handle that...
        if (compareInfo.Compare(source, index, match.Length, match, 0, match.Length, CompareOptions.IgnoreCase) == 0)
        {
            return match.Length;
        }

        return 0;
    }
}

(Sadece referans için dahil edilmiştir, bildiğiniz gibi doğru şekilde karşılaştırılmayacak olan koddur)

Bu yöntemin aşağıdaki varyantı , çerçeve tarafından sağlanan StringInfo.GetNextTextElement kullanır . Buradaki fikir, bir eşleşme bulmak için metin öğesini metin öğesine göre karşılaştırmak ve bulunursa, kaynak dizedeki eşleşen karakterlerin gerçek sayısını döndürmektir:

// Using StringInfo.GetNextTextElement to match by text elements instead of characters
static int IsMatch_New(string source, int index, string match, CompareInfo compareInfo)
{
    int sourceIndex = index;
    int matchIndex = 0;

    // Loop until we reach the end of source or match
    while (sourceIndex < source.Length && matchIndex < match.Length)
    {
        // Get text elements at the current positions of source and match
        // Normally that will be just one character but may be more in case of Unicode combining characters
        string sourceElem = StringInfo.GetNextTextElement(source, sourceIndex);
        string matchElem = StringInfo.GetNextTextElement(match, matchIndex);

        // Compare the current elements.
        if (compareInfo.Compare(sourceElem, matchElem, CompareOptions.IgnoreCase) != 0)
        {
            return 0; // No match
        }

        // Advance in source and match (by number of characters)
        sourceIndex += sourceElem.Length;
        matchIndex += matchElem.Length;
    }

    // Check if we reached end of source and not end of match
    if (matchIndex != match.Length)
    {
        return 0; // No match
    }

    // Found match. Return number of matching characters from source.
    return sourceIndex - index;
}

Bu yöntem, en azından benim test durumlarıma göre gayet iyi çalışıyor (temelde sağladığınız dizelerin birkaç çeşidini test ediyor: "b\u00e9d"ve "be\u0301d").

Bununla birlikte, GetNextTextElement yöntemi, her metin öğesi için bir alt dize oluşturur, bu nedenle bu uygulama, performans üzerinde bir etkiye sahip olacak çok sayıda alt dize karşılaştırması gerektirir.

Bu yüzden, GetNextTextElement kullanmayan ancak bunun yerine karakterlerde gerçek eşleşme uzunluğunu bulmak için Unicode birleştirme karakterlerini atlayan başka bir varyant oluşturdum :

// This should be faster
static int IsMatch_Faster(string source, int index, string match, CompareInfo compareInfo)
{
    int sourceLength = source.Length;
    int matchLength = match.Length;
    int sourceIndex = index;
    int matchIndex = 0;

    // Loop until we reach the end of source or match
    while (sourceIndex < sourceLength && matchIndex < matchLength)
    {
        sourceIndex += GetTextElemLen(source, sourceIndex, sourceLength);
        matchIndex += GetTextElemLen(match, matchIndex, matchLength);
    }

    // Check if we reached end of source and not end of match
    if (matchIndex != matchLength)
    {
        return 0; // No match
    }

    // Check if we've found a match
    if (compareInfo.Compare(source, index, sourceIndex - index, match, 0, matchIndex, CompareOptions.IgnoreCase) != 0)
    {
        return 0; // No match
    }

    // Found match. Return number of matching characters from source.
    return sourceIndex - index;
}

Bu yöntemde aşağıdaki iki yardımcı kullanılır:

static int GetTextElemLen(string str, int index, int strLen)
{
    bool stop = false;
    int elemLen;

    for (elemLen = 0; index < strLen && !stop; ++elemLen, ++index)
    {
        stop = !IsCombiningCharacter(str, index);
    }

    return elemLen;
}

static bool IsCombiningCharacter(string str, int index)
{
    switch (CharUnicodeInfo.GetUnicodeCategory(str, index))
    {
        case UnicodeCategory.NonSpacingMark:
        case UnicodeCategory.SpacingCombiningMark:
        case UnicodeCategory.EnclosingMark:
            return true;

        default:
            return false;
    }
}

Ben herhangi bir kıyaslama yapmadım, bu yüzden daha hızlı yöntemin gerçekten daha hızlı olup olmadığını gerçekten bilmiyorum. Ayrıca herhangi bir genişletilmiş test yapmadım.

Ancak bu, Unicode birleştirme karakterleri içerebilen dizeler için kültürel hassas alt dize eşleştirmesinin nasıl gerçekleştirileceği konusundaki sorunuza yanıt vermelidir.

Bunlar kullandığım test durumları:

static Tuple<string, int, string, int>[] tests = new []
{
    Tuple.Create("x b\u00e9d y", 2, "be\u0301d", 3),
    Tuple.Create("x be\u0301d y", 2, "b\u00e9d", 4),

    Tuple.Create("x b\u00e9d", 2, "be\u0301d", 3),
    Tuple.Create("x be\u0301d", 2, "b\u00e9d", 4),

    Tuple.Create("b\u00e9d y", 0, "be\u0301d", 3),
    Tuple.Create("be\u0301d y", 0, "b\u00e9d", 4),

    Tuple.Create("b\u00e9d", 0, "be\u0301d", 3),
    Tuple.Create("be\u0301d", 0, "b\u00e9d", 4),

    Tuple.Create("b\u00e9", 0, "be\u0301d", 0),
    Tuple.Create("be\u0301", 0, "b\u00e9d", 0),
};

Tuple değerleri şunlardır:

  1. Kaynak dizesi (samanlık)
  2. Kaynaktaki başlangıç ​​konumu.
  3. Eşleşme dizesi (iğne).
  4. Beklenen maç uzunluğu.

Bu testleri üç yöntemde çalıştırmak şu sonucu verir:

Test #0: Orignal=BAD; New=OK; Faster=OK
Test #1: Orignal=BAD; New=OK; Faster=OK
Test #2: Orignal=BAD; New=OK; Faster=OK
Test #3: Orignal=BAD; New=OK; Faster=OK
Test #4: Orignal=BAD; New=OK; Faster=OK
Test #5: Orignal=BAD; New=OK; Faster=OK
Test #6: Orignal=BAD; New=OK; Faster=OK
Test #7: Orignal=BAD; New=OK; Faster=OK
Test #8: Orignal=OK; New=OK; Faster=OK
Test #9: Orignal=OK; New=OK; Faster=OK

Son iki test, kaynak dizenin eşleşme dizesinden daha kısa olduğu durumu test ediyor. Bu durumda orijinal (Noda zamanı) yöntemi de başarılı olacaktır.


Bunun için çok teşekkür ederim. Ne kadar iyi performans gösterdiğini görmek için ayrıntılı olarak incelemem gerekecek, ancak harika bir başlangıç ​​noktası gibi görünüyor. Unicode hakkında (kodun kendisinde) gerekeceğini umduğumdan daha fazla bilgi , ancak platform gerekli olanı yapmazsa, bu konuda yapabileceğim pek bir şey yok :(
Jon Skeet

@JonSkeet: Yardımcı olduğuma sevindim! Ve evet, Unicode desteğiyle alt dize eşleşmesi kesinlikle çerçeveye dahil edilmelidir ...
Mårten Wikström
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.