मैं एक स्ट्रिंग के बीच से एक संस्कृति-संवेदनशील "स्टार्ट-विद" ऑपरेशन कैसे कर सकता हूं?


106

मेरे पास एक आवश्यकता है जो अपेक्षाकृत अस्पष्ट है, लेकिन यह महसूस करता है कि बीसीएल का उपयोग करना संभव होना चाहिए।

संदर्भ के लिए, मैं Noda Time में दिनांक / समय स्ट्रिंग को पार्स कर रहा हूं । मैं इनपुट स्ट्रिंग के भीतर अपनी स्थिति के लिए एक तार्किक कर्सर बनाए रखता हूं। तो जबकि पूरा स्ट्रिंग "3 जनवरी 2013" हो सकता है, तार्किक कर्सर 'J' पर हो सकता है।

अब, मुझे महीने के नाम को पार्स करने की आवश्यकता है, संस्कृति के सभी ज्ञात महीनों के नामों की तुलना करते हुए:

  • संस्कृति संवेदनशीलता
  • केस-insensitively
  • कर्सर के बिंदु से (बाद में नहीं; मैं देखना चाहता हूं कि क्या कर्सर उम्मीदवार महीने का नाम "देख रहा है")
  • जल्दी से
  • ... और मुझे यह जानने की आवश्यकता है कि कितने वर्णों का उपयोग किया गया था

यह करने के लिए वर्तमान कोड आम तौर पर काम करता है, का उपयोग कर CompareInfo.Compare। यह प्रभावी रूप से ऐसा है (केवल मिलान वाले भाग के लिए - वास्तविक चीज़ में अधिक कोड है, लेकिन यह मैच के लिए प्रासंगिक नहीं है):

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

हालाँकि, यह उम्मीदवार और उस क्षेत्र पर निर्भर करता है जिसकी हम समान लंबाई के साथ तुलना करते हैं। अधिकांश समय ठीक है , लेकिन कुछ विशेष मामलों में ठीक नहीं है। मान लीजिए कि हमारे पास कुछ ऐसा है:

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

अब मेरी तुलना असफल हो जाएगी। मैं उपयोग कर सकता हूं IsPrefix:

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

परंतु:

  • इसके लिए मुझे एक विकल्प बनाना होगा, जिससे मैं वास्तव में बचूँ। (मैं नोडा टाइम को प्रभावी रूप से एक सिस्टम लाइब्रेरी के रूप में देख रहा हूं; कुछ ग्राहकों के लिए प्रदर्शन का प्रदर्शन महत्वपूर्ण हो सकता है।)
  • यह मुझे नहीं बताता कि बाद में कर्सर को कैसे आगे बढ़ाना है

वास्तव में, मुझे दृढ़ता से संदेह है कि यह बहुत बार नहीं आएगा ... लेकिन मैं वास्तव में यहां सही काम करना चाहूंगा । मैं भी वास्तव में एक यूनिकोड विशेषज्ञ बनने के बिना या खुद इसे लागू करने में सक्षम होना चाहूंगा :)

( यदि कोई भी अंतिम निष्कर्ष का पालन करना चाहता है, तो नोडा टाइम में बग 210 के रूप में उठाया गया ।)

मुझे सामान्यीकरण का विचार पसंद है। मुझे यह जांचने की आवश्यकता है कि ए) शुद्धता और बी) प्रदर्शन के लिए विस्तार से। यह मानते हुए कि मैं इसे सही ढंग से काम कर सकता हूं , मुझे अभी भी यकीन नहीं है कि यह कैसे बदल जाएगा या नहीं - यह उस तरह की चीज है जो वास्तव में वास्तविक जीवन में कभी नहीं आएगी, लेकिन मेरे सभी उपयोगकर्ताओं के प्रदर्शन को चोट पहुंचा सकती है: (

मैंने BCL को भी चेक किया है - जो इसे ठीक से हैंडल करने के लिए प्रकट नहीं होता है। नमूना कोड:

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

कस्टम महीने के नाम को "बेड" के पाठ मान के साथ सिर्फ "बेड" में बदलना ठीक है।

ठीक है, कुछ और डेटा बिंदु:

  • उपयोग करने की लागत Substringऔर IsPrefixमहत्वपूर्ण है लेकिन भयानक नहीं है। मेरे विकास लैपटॉप पर "शुक्रवार 12 अप्रैल 2013 20:28:42" के नमूने में, यह पार्स संचालन की संख्या को बदलता है जिसे मैं एक सेकंड में लगभग 460K से लगभग 400K तक निष्पादित कर सकता हूं। यदि संभव हो तो मैं उस मंदी से बचना चाहता हूँ, लेकिन यह बहुत बुरा नहीं है

  • मेरे विचार से सामान्यीकरण कम संभव है - क्योंकि यह पोर्टेबल कक्षा पुस्तकालयों में उपलब्ध नहीं है। मैं संभवतः यह इस्तेमाल कर सकते हैं सिर्फ गैर पीसीएल बनाता है के लिए, पीसीएल की इजाजत दी थोड़ा कम सही होने के लिए बनाता है। सामान्यीकरण के लिए परीक्षण का प्रदर्शन हिट ( string.IsNormalized) प्रति सेकंड लगभग 445K कॉल के लिए प्रदर्शन लेता है, जिसके साथ मैं रह सकता हूं। मुझे अभी भी यकीन नहीं है कि मुझे वह सब कुछ करना है जो मुझे इसकी आवश्यकता है - उदाहरण के लिए, एक महीने का नाम "sure" होना चाहिए जो कई संस्कृतियों में "एसएस" से मेल खाना चाहिए, मेरा मानना ​​है ... और सामान्यीकरण ऐसा नहीं करता है।


जबकि मैं एक विकल्प बनाने के प्रदर्शन हिट से बचने के लिए आपकी इच्छा को समझता हूं, ऐसा करना सबसे अच्छा हो सकता है, लेकिन पहले गेम में सब कुछ एक चुने हुए यूनिकोड सामान्यीकरण फॉर्म को FIRST के रूप में स्थानांतरित करके और फिर आप जानते हुए "पॉइंट-बाय-पॉइंट" चल सकते हैं "। शायद डी-फॉर्म।
आईडीसोप्लॉरी

@ एडिशनल: हां, मैंने इस बारे में आश्चर्य किया। जाहिर है मैं महीने के नाम को पहले से ही सामान्य कर सकता हूं। कम से कम मैं एक बार सामान्यीकरण कर सकता हूं। मुझे आश्चर्य है कि अगर सामान्यीकरण प्रक्रिया जांचती है कि क्या कुछ भी करने की आवश्यकता है। मेरे पास सामान्यीकरण का बहुत अनुभव नहीं है - निश्चित रूप से देखने के लिए एक अवसर है।
जॉन स्कीट

1
यदि आपका textसमय बहुत लंबा नहीं है, तो आप कर सकते हैं if (compareInfo.IndexOf(text, candidate, position, options) == position)msdn.microsoft.com/en-us/library/ms143031.aspx लेकिन अगर textयह बहुत लंबा है, तो यह खोज करने में बहुत समय बर्बाद करने वाला है कि इसे कहां तक पहुंचाना है।
जिम मेंथेल

1
बस का उपयोग कर बाईपास Stringवर्ग सभी को इस उदाहरण में और एक का उपयोग Char[]सीधे। आप अधिक कोड लिखना समाप्त कर देंगे, लेकिन ऐसा तब होता है जब आप उच्च प्रदर्शन चाहते हैं ... या हो सकता है कि आप C ++ / CLI ;-) में प्रोग्रामिंग कर रहे हों
intrepidis

1
क्या आप तुलना करेंगे। IgnoreNonSpace आपके लिए स्वचालित रूप से इस बात का ध्यान नहीं रखता है? यह मेरे लिए (docco से, इस आईपैड खेद है! से परीक्षण करने की स्थिति में नहीं है) लगता है जैसे कि यह एक (हो सकता है ?) उस विकल्प के लिए यूज-केस। " इंगित करता है कि स्ट्रिंग तुलना को वर्णों जैसे संयोजन वर्णों के निरूपण को अनदेखा करना चाहिए। "
सेपस्टर

जवाबों:


41

मैं कई की समस्या पर विचार करूंगा <-> एक / कई केसपेपिंग पहले और अलग-अलग सामान्यीकरण रूपों को संभालने से।

उदाहरण के लिए:

x heiße y
  ^--- cursor

मेल खाता है heisseलेकिन फिर कर्सर 1 को बहुत अधिक स्थानांतरित करता है। तथा:

x heisse y
  ^--- cursor

मेल खाता है heißeलेकिन फिर कर्सर 1 को बहुत कम ले जाता है।

यह किसी भी ऐसे पात्र पर लागू होगा जिसमें साधारण वन-टू-वन मैपिंग नहीं है।

आपको उस प्रतिस्थापन की लंबाई को जानना होगा जो वास्तव में मेल खाता था। लेकिन Compare, IndexOf.. उस जानकारी को दूर फेंक दो। यह नियमित अभिव्यक्तियों के साथ संभव हो सकता है, लेकिन कार्यान्वयन पूर्ण केस फोल्डिंग नहीं करता है और इसलिए केस-असंवेदनशील मोड में भी मेल नहीं खाता ßहै और करते हैं। और वैसे भी हर उम्मीदवार के लिए नए regexes बनाना शायद महंगा होगा।ss/SS.Compare.IndexOf

इसका सबसे सरल उपाय यह है कि केस को फोल्ड किए गए फॉर्म में केवल आंतरिक रूप से स्टोर करें और केस फोल्ड किए गए उम्मीदवारों के साथ बाइनरी तुलना करें। तब आप कर्सर को ठीक से स्थानांतरित कर सकते हैं .Lengthक्योंकि कर्सर आंतरिक प्रतिनिधित्व के लिए है। तुम भी खो प्रदर्शन का सबसे वापस उपयोग करने के लिए नहीं होने से मिलता है CompareOptions.IgnoreCase

दुर्भाग्य से वहाँ कोई मामला तह समारोह में बनाया गया है और गरीब आदमी के मामले तह काम नहीं करता है क्योंकि वहाँ कोई पूरा मामला मानचित्रण है - ToUpperविधि ßमें बदल नहीं है SS

उदाहरण के लिए यह जावा में काम करता है (और जावास्क्रिप्ट में भी), दी गई स्ट्रिंग जो कि सामान्य फॉर्म सी में है:

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

ध्यान दें कि जावा की अनदेखी मामले की तुलना सी # की तरह फुल केस फोल्डिंग नहीं करती है CompareOptions.IgnoreCase। इसलिए वे इस संबंध में विपरीत हैं: जावा पूर्ण केसमैपिंग करता है, लेकिन साधारण केस फोल्डिंग - सी # सरल केसमैपिंग करता है, लेकिन पूर्ण केस फोल्डिंग।

तो यह संभावना है कि आप उन्हें उपयोग करने से पहले अपने तार को मोड़ने के लिए एक 3 पार्टी लाइब्रेरी की आवश्यकता है।


कुछ भी करने से पहले आपको यह सुनिश्चित करना होगा कि आपके तार सामान्य रूप सी में हैं। आप लैटिन लिपि के लिए अनुकूलित इस प्रारंभिक त्वरित जांच का उपयोग कर सकते हैं:

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

यह झूठी सकारात्मकता देता है लेकिन गलत नकारात्मक नहीं है, मुझे उम्मीद नहीं है कि लैटिन स्क्रिप्ट वर्णों का उपयोग करते समय 460k पार्स / एस को धीमा कर देगा, भले ही इसे हर स्ट्रिंग पर प्रदर्शन करने की आवश्यकता हो। एक झूठे सकारात्मक के साथ आप IsNormalizedएक सच्चे नकारात्मक / सकारात्मक प्राप्त करने के लिए उपयोग करेंगे और उसके बाद ही यदि आवश्यक हो तो सामान्य करें।


इसलिए निष्कर्ष में, प्रसंस्करण को सामान्य रूप C पहले सुनिश्चित करना है, फिर केस फोल्ड। संसाधित तार के साथ द्विआधारी तुलना करें और कर्सर ले जाएं जैसा कि आप वर्तमान में इसे स्थानांतरित कर रहे हैं।


इसके लिए धन्यवाद - मुझे सामान्य रूप सी में और अधिक विस्तार से देखने की आवश्यकता होगी, लेकिन ये महान संकेत हैं। मुझे लगता है कि मैं पीसीएल के तहत "यह सही ढंग से काम नहीं करता है" (जो सामान्यीकरण प्रदान नहीं करता है) के साथ रह सकता है। केस-फोल्डिंग के लिए एक 3 पार्टी लाइब्रेरी का उपयोग करना यहाँ ओवरकिल होगा - वर्तमान में हमारे पास कोई 3 पार्टी निर्भरता नहीं है, और एक कोने के मामले के लिए केवल एक को पेश करना है जो कि बीसीएल भी नहीं संभालती है एक दर्द होगा। संभवतः मामला-तह संस्कृति के प्रति संवेदनशील है, btw (जैसे तुर्की)?
जॉन स्कीट

2
@JonSkeet हाँ, तुर्क केसफॉल्ड मैपिंग में अपने स्वयं के मोड का हकदार है: P CaseFolding.txt
Esailija

इस उत्तर में एक मौलिक दोष है, जिसमें यह तात्पर्य है कि वर्ण लिगुरेट्स (और इसके विपरीत) में मैप करते हैं, केवल केस-फोल्डिंग के समय। यह मामला नहीं है; ऐसे बंधन हैं जो आवरण के बावजूद वर्णों के बराबर माने जाते हैं। उदाहरण के लिए, एन-यूएस संस्कृति के तहत, æइसके बराबर है aeऔर इसके बराबर है ffi। सी-सामान्यकरण लिगमेंट्स को बिल्कुल भी संभाल नहीं करता है, क्योंकि यह केवल संगतता मैपिंग (जो आमतौर पर वर्णों के संयोजन तक सीमित है) की अनुमति देता है।
डगलस

केसी- और केडी-सामान्यकरण कुछ लिगचर को संभालते हैं, जैसे कि , लेकिन दूसरों को याद करते हैं, जैसे कि æ। इस मुद्दे को संस्कृतियों के बीच विसंगतियों द्वारा बदतर बनाया गया है - एन-यूएस के तहत æबराबर है ae, लेकिन दा-डीके के तहत नहीं है , जैसा कि स्ट्रिंग्स के लिए एमएसडीएन प्रलेखन के तहत चर्चा की गई है । इस प्रकार, सामान्यीकरण (किसी भी रूप में) और केस-मैपिंग इस समस्या का पर्याप्त समाधान नहीं है।
डगलस

मेरी पिछली टिप्पणी में छोटा सुधार: सी-सामान्यीकरण केवल कैनोनिकल मैपिंग (जैसे कि वर्णों के संयोजन के लिए) की अनुमति देता है , न कि संगतता मैपिंग (जैसे लिगर्स के लिए)।
डगलस

21

देखें कि क्या यह आवश्यकता पूरी होती है ..:

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.Compareकेवल एक बार के sourceसाथ शुरू होता है prefix; यदि ऐसा नहीं होता, तो IsPrefixलौटता है -1; अन्यथा, पात्रों की लंबाई का उपयोग किया जाता है source

हालांकि, मैं वेतन वृद्धि को छोड़कर कोई अंदाज़ा नहीं है length2द्वारा 1निम्नलिखित मामले से:

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

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

अपडेट :

मैंने थोड़ा सा सुधार करने की कोशिश की, लेकिन यह साबित नहीं हुआ कि निम्नलिखित कोड में बग है या नहीं:

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

मैंने विशेष मामले के साथ परीक्षण किया, और तुलना लगभग 3 तक कम हो गई।


मैं वास्तव में इस तरह लूप करने के लिए नहीं था। जल्दी से बाहर के साथ यह केवल लूप की आवश्यकता होगी अगर यह कुछ पाया जाता है, लेकिन मुझे अभी भी 8 स्ट्रिंग तुलना करने की ज़रूरत नहीं है उदाहरण के लिए "फरवरी" से मिलान करें। ऐसा लगता है कि एक बेहतर तरीका होना चाहिए। इसके अलावा, प्रारंभिक IndexOfऑपरेशन को प्रारंभिक स्थिति से पूरे स्ट्रिंग को देखना होगा, जो इनपुट स्ट्रिंग लंबे होने पर एक प्रदर्शन दर्द होगा।
जॉन स्कीट

@JonSkeet: धन्यवाद। शायद यह पता लगाने के लिए कुछ जोड़ा जा सकता है कि क्या लूप को कम किया जा सकता है। मैं उसके बारे में सोचूंगा।
केन किन

@JonSkeet: क्या आप प्रतिबिंब का उपयोग करने पर विचार करेंगे? चूंकि मैंने विधियों में पता लगाया है, इसलिए वे दूर नहीं जाने वाले मूल तरीकों का आह्वान करते हैं।
केन किन

3
वास्तव में। Noda Time यूनिकोड विवरण के व्यवसाय में नहीं आना चाहता है :)
जॉन स्कीट

2
मैंने एक बार इसी तरह की समस्या को हल किया है (HTML में सर्च-स्ट्रिंग हाइलाइटिंग)। मैंने भी ऐसा ही किया। आप लूप को ट्यून कर सकते हैं और रणनीति को इस तरह से खोज सकते हैं जिससे पहले संभावित मामलों की जांच करके इसे बहुत जल्दी पूरा किया जा सके। इसके बारे में अच्छी बात यह है कि यह पूरी तरह से सही है और आपके कोड में कोई यूनिकोड विवरण लीक नहीं हुआ है।
usr

9

यह वास्तव में सामान्यीकरण के बिना और उपयोग किए बिना संभव है IsPrefix

हमें समान संख्या में वर्णों की तुलना में समान पाठ तत्वों की तुलना करने की आवश्यकता है , लेकिन फिर भी मिलान वाले वर्णों की संख्या लौटाते हैं।

मैंने Noda Time में ValueCursor.csMatchCaseInsensitive से विधि की एक प्रति बनाई है और इसे थोड़ा संशोधित किया है ताकि इसका उपयोग स्थिर अवस्था में किया जा सके:

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

(सिर्फ संदर्भ के लिए शामिल है, यह कोड है जो ठीक से तुलना नहीं करेगा जैसा कि आप जानते हैं)

उस पद्धति का निम्न प्रकार StringInfo.GetNextTextElement का उपयोग करता है जो कि रूपरेखा द्वारा प्रदान किया गया है। मैच खोजने के लिए टेक्स्ट एलिमेंट की तुलना टेक्स्ट एलिमेंट से की जाती है और अगर सोर्स स्ट्रिंग में मैचिंग कैरेक्टर्स की वास्तविक संख्या वापस मिलती है:

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

यह विधि मेरे परीक्षण मामलों के अनुसार कम से कम ठीक काम करती है (जो मूल रूप से आपके द्वारा प्रदान किए गए तार के कुछ रूपों का परीक्षण करती है: "b\u00e9d"और "be\u0301d")।

हालाँकि, GetNextTextElement विधि प्रत्येक पाठ तत्व के लिए एक विकल्प बनाती है, इसलिए इस कार्यान्वयन के लिए विकल्प की तुलना करने की आवश्यकता होती है - जिसका प्रदर्शन पर प्रभाव पड़ेगा।

इसलिए, मैंने एक और संस्करण बनाया जो GetNextTextElement का उपयोग नहीं करता है, बल्कि इसके बजाय पात्रों में वास्तविक मैच की लंबाई खोजने के लिए यूनिकोड संयोजन पात्रों पर छोड़ देता है:

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

यह विधि निम्नलिखित दो सहायकों का उपयोग करती है:

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

मैंने कोई बेंच मार्किंग नहीं की है, इसलिए मुझे वास्तव में नहीं पता है कि वास्तव में तेज विधि वास्तव में तेज है या नहीं। न ही मैंने कोई विस्तारित परीक्षण किया है।

लेकिन यह आपके प्रश्न का उत्तर देना चाहिए कि स्ट्रिंग्स के लिए सांस्कृतिक संवेदनशील प्रतिस्थापन विकल्प का प्रदर्शन कैसे किया जाए जिसमें यूनिकोड संयोजन वर्ण शामिल हो सकते हैं।

ये मेरे द्वारा उपयोग किए गए परीक्षण मामले हैं:

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

टपल मूल्य हैं:

  1. स्रोत स्ट्रिंग (हैस्टैक)
  2. स्रोत में प्रारंभिक स्थिति।
  3. मैच स्ट्रिंग (सुई)।
  4. अपेक्षित मिलान लंबाई।

तीन तरीकों पर उन परीक्षणों को चलाने से यह परिणाम मिलता है:

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

पिछले दो परीक्षण केस का परीक्षण कर रहे हैं जब स्रोत स्ट्रिंग मैच स्ट्रिंग से कम है। इस स्थिति में मूल (Noda time) विधि भी सफल होगी।


इस के लिए बहुत बहुत धन्यवाद। मुझे यह देखने के लिए विस्तार से देखना होगा कि यह कितना अच्छा प्रदर्शन करता है, लेकिन यह एक महान शुरुआती बिंदु जैसा दिखता है। यूनिकोड का अधिक ज्ञान (स्वयं कोड में) की तुलना में मुझे उम्मीद होगी कि यदि प्लेटफॉर्म की आवश्यकता नहीं है, तो बहुत कुछ ऐसा नहीं है जिसके बारे में मैं कर सकता हूं :(
जॉन स्कीट

@JonSkeet: खुशी किसी भी मदद की हो! और हाँ, यूनिकोड समर्थन के साथ मेल खाते विकल्प को निश्चित रूप से फ्रेमवर्क में शामिल किया जाना चाहिए ...
मैरटेन विकस्ट्रॉम
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.