इस तार की लंबाई उसमें वर्णों की संख्या से अधिक लंबी क्यों है?


145

यह कोड:

string a = "abc";
string b = "A𠈓C";
Console.WriteLine("Length a = {0}", a.Length);
Console.WriteLine("Length b = {0}", b.Length);

आउटपुट:

Length a = 3
Length b = 4

क्यों? केवल एक चीज जो मैं सोच सकता था, वह यह है कि चीनी चरित्र 2 बाइट्स लंबा है और यह .Lengthविधि बाइट काउंट लौटाती है।


10
मुझे कैसे पता चला कि यह सिर्फ शीर्षक को देखने से एक सरोगेट जोड़ी की समस्या थी। आह, अच्छा 'ol System.Globalization आपका सहयोगी है!
क्रिस क्रॉफिस

9
यह यूटीएफ -16 में 4 बाइट्स लंबा है, न कि 2
phuclv

char 𠈓का दशमलव मान 131603 है, और जैसा कि char अहस्ताक्षरित बाइट्स हैं, इसका मतलब है कि आप 4 के बजाय 2 वर्णों में उस मान को प्राप्त कर सकते हैं (अहस्ताक्षरित 16 बिट मूल्य अधिकतम 65535 (या 65536 रूपांतर) है और इसका प्रतिनिधित्व करने के लिए 2 वर्णों का उपयोग करने की अनुमति देता है 65536 * 2 (131072) नहीं बल्कि 65536 * 65536 विविधताओं की अधिकतम संख्या के लिए (4,294,967,296, प्रभावी रूप से 32 बिट मूल्य)
GMasucci

3
@GMAsucci: यह UTF-16 में 2 वर्ण है, लेकिन 4 बाइट्स, क्योंकि UTF16 वर्ण 2 बाइट आकार में है, अन्यथा यह 65536 रूपांतरों को संग्रहीत नहीं कर सका, लेकिन केवल 256.
कैसरलडी

4
मैं महान लेख 'संपूर्ण न्यूनतम हर सॉफ्टवेयर डेवलपर को पढ़ने की सलाह देता हूं, यूनिकोड और कैरेक्टर सेट (कोई बहाना नहीं!) के बारे में सकारात्मक रूप से जानना चाहिए। joelonsoftware.com/articles/Unicode.html
ItsMe

जवाबों:


232

बाकी सभी लोग सतह का जवाब दे रहे हैं, लेकिन एक गहरा तर्क भी है: "वर्ण" की संख्या एक कठिन-से-परिभाषित प्रश्न है और गणना करने के लिए आश्चर्यजनक रूप से महंगा हो सकता है, जबकि एक लंबाई संपत्ति तेजी से होनी चाहिए।

इसे परिभाषित करना मुश्किल क्यों है? खैर, कुछ विकल्प हैं और कोई भी वास्तव में दूसरे की तुलना में अधिक वैध नहीं है:

  • कोड इकाइयों की संख्या (बाइट्स या अन्य निश्चित आकार के डेटा चंक; सी # और विंडोज आमतौर पर यूटीएफ -16 का उपयोग करते हैं इसलिए यह दो-बाइट टुकड़ों की संख्या लौटाता है) निश्चित रूप से प्रासंगिक है, क्योंकि कंप्यूटर को अभी भी उस रूप में डेटा से निपटने की आवश्यकता है कई उद्देश्यों के लिए (एक फ़ाइल में लिखना, उदाहरण के लिए, पात्रों के बजाय बाइट्स के बारे में परवाह है)

  • यूनिकोड कोडपॉइंट्स की संख्या को गणना करना काफी आसान है (हालांकि ओ (एन) क्योंकि आप सरोगेट जोड़े के लिए स्ट्रिंग को स्कैन करेंगे) और पाठ संपादक के लिए मायने रख सकते हैं .... लेकिन वास्तव में वर्णों की संख्या के समान नहीं है। स्क्रीन पर मुद्रित (जिसे ग्रेफेम कहा जाता है)। उदाहरण के लिए, कुछ उच्चारण अक्षरों को दो रूपों में दर्शाया जा सकता है: एक एकल कोडपॉइंट, या दो बिंदुओं को एक साथ जोड़ा जाता है, एक पत्र का प्रतिनिधित्व करता है, और एक कह रहा है "मेरे साथी पत्र में एक उच्चारण जोड़ें"। क्या जोड़ी दो किरदार होगी या एक? आप इसकी सहायता से तार को सामान्य कर सकते हैं, लेकिन सभी मान्य अक्षरों में एक ही कोडपॉइंट प्रतिनिधित्व नहीं है।

  • यहां तक ​​कि अंगूर की संख्या एक मुद्रित स्ट्रिंग की लंबाई के समान नहीं है, जो अन्य कारकों के बीच फ़ॉन्ट पर निर्भर करती है, और चूंकि कुछ अक्षर कई फोंट (कर्लिंग) में कुछ ओवरलैप के साथ मुद्रित होते हैं, स्क्रीन पर एक स्ट्रिंग की लंबाई जरूरी नहीं कि वैसे भी अंगूर की लंबाई के योग के बराबर है!

  • कुछ यूनिकोड बिंदु पारंपरिक अर्थों में भी वर्ण नहीं हैं, बल्कि कुछ प्रकार के नियंत्रण मार्कर हैं। बाइट ऑर्डर मार्कर या राइट-टू-लेफ्ट इंडिकेटर की तरह। क्या ये गिनते हैं?

संक्षेप में, एक स्ट्रिंग की लंबाई वास्तव में एक हास्यास्पद जटिल प्रश्न है और इसकी गणना करने में बहुत अधिक सीपीयू समय के साथ-साथ डेटा टेबल भी लग सकते हैं।

इसके अलावा, क्या बात है? ये मेट्रिक्स क्यों मायने रखता है? ठीक है, केवल आप ही जवाब दे सकते हैं कि आपके मामले के लिए, लेकिन व्यक्तिगत रूप से, मुझे लगता है कि वे आम तौर पर अप्रासंगिक हैं। डेटा प्रविष्टि को सीमित करना मुझे अधिक तार्किक रूप से बाइट सीमा के द्वारा होता है, क्योंकि इसे वैसे भी स्थानांतरित या संग्रहीत करने की आवश्यकता होती है। डिस्प्ले साइड सॉफ्टवेयर द्वारा डिस्प्ले साइज़ को सीमित करना बेहतर होता है - यदि आपके पास संदेश के लिए 100 पिक्सेल हैं, तो आप कितने वर्ण फिट करते हैं, यह फॉन्ट आदि पर निर्भर करता है, जो कि डेटा लेयर सॉफ्टवेयर द्वारा वैसे भी ज्ञात नहीं है। अंत में, यूनिकोड मानक की जटिलता को देखते हुए, आप शायद किनारे के मामलों में कीड़े होने जा रहे हैं, अगर आप कुछ और कोशिश करते हैं।

तो यह एक सामान्य प्रश्न है जिसमें बहुत सारे सामान्य उद्देश्य का उपयोग नहीं होता है। कोड इकाइयों की संख्या की गणना करने के लिए तुच्छ है - यह अंतर्निहित डेटा सरणी की लंबाई है - और एक सामान्य नियम के रूप में सबसे सार्थक / उपयोगी है, एक साधारण परिभाषा के साथ।

यही कारण है कि "स्पष्टीकरण के कारण सतह स्पष्टीकरण से परे bलंबाई 4है क्योंकि प्रलेखन ऐसा कहता है"।


9
अनिवार्य रूप से '.Length' वह नहीं है जो अधिकांश कोडर सोचते हैं। हो सकता है कि अधिक विशिष्ट गुणों (जैसे ग्लिफ़काउंट) और लंबाई को अप्रचलित के रूप में चिह्नित किया जाना चाहिए!
redcalx

8
@ लॉकर मैं सहमत हूं, लेकिन मुझे नहीं लगता कि Lengthअप्रचलित होना चाहिए, सरणियों के साथ सादृश्य बनाए रखने के लिए।
क्रोल्टन

2
@ लॉस्टर यह अप्रचलित नहीं होना चाहिए। अजगर एक बहुत समझ में आता है और कोई भी यह सवाल नहीं करता है।
सिमोनजैक

1
मुझे लगता है .Length बहुत मायने रखती है और एक प्राकृतिक संपत्ति है, जब तक आप समझते हैं कि यह क्या है और यह इस तरह से क्यों है। फिर यह किसी भी अन्य सरणी की तरह काम करता है (कुछ भाषाओं में डी की तरह, एक स्ट्रिंग का शाब्दिक अर्थ है जहां तक ​​भाषा का संबंध है और यह वास्तव में अच्छी तरह से काम करता है)
एडम डी। रूप्पे

4
यह सच नहीं है (एक आम गलतफहमी) - UTF-32 के साथ, lengthInBytes / 4 कोड अंक की संख्या देगा , लेकिन यह "वर्ण" या अंगूर की संख्या के समान नहीं है। LATIN SMALL LETTER E पर विचार करें, इसके बाद एक COMBINING DIAERESIS ... जो एक एकल वर्ण के रूप में प्रिंट करता है, इसे एक ही कोडपॉइंट के लिए सामान्यीकृत किया जा सकता है, लेकिन यह अभी भी दो इकाइयों लंबा है, UTF-32 में भी।
एडम डी। रूप्पे

62

संपत्ति के प्रलेखन से String.Length:

लंबाई की संपत्ति इस उदाहरण में चार वस्तुओं की संख्या लौटाती है , यूनिकोड वर्णों की संख्या नहीं। कारण यह है कि एक यूनिकोड वर्ण को एक से अधिक चार द्वारा दर्शाया जा सकता है । प्रत्येक चार के बजाय प्रत्येक यूनिकोड वर्ण के साथ काम करने के लिए System.Globalization.StringInfo वर्ग का उपयोग करें ।


3
जावा उसी तरह से व्यवहार करता है (4 के लिए मुद्रण भी String b), क्योंकि यह चार्ट सरणियों में UTF-16 प्रतिनिधित्व का उपयोग करता है। यह UTF-8 में एक 4 बाइट चरित्र है।
माइकल

32

सूचकांक 1 में आपका चरित्र "A𠈓C"एक सरोगेटेयर है

याद रखने की महत्वपूर्ण बात यह है कि सरोगेट जोड़े 32-बिट एकल वर्णों का प्रतिनिधित्व करते हैं ।

आप इस कोड को आज़मा सकते हैं और यह वापस आ जाएगा True

Console.WriteLine(char.IsSurrogatePair("A𠈓C", 1));

Char.IsSurrogatePair विधि (स्ट्रिंग, Int32)

trueअगर s पैरामीटर में स्थिति इंडेक्स और इंडेक्स + 1 पर आसन्न अक्षर शामिल हैं , और स्थिति इंडेक्स पर वर्ण का संख्यात्मक मान U + D800 से U + DBFF के माध्यम से है, और स्थिति इंडेक्स + 1 से वर्ण का संख्यात्मक मान U से है। U + DFFF के माध्यम से + DC00; अन्यथा, false

इसे आगे String.Length संपत्ति में समझाया गया है:

लंबाई की संपत्ति इस उदाहरण में चार वस्तुओं की संख्या लौटाती है , यूनिकोड वर्णों की संख्या नहीं।कारण यह है कि एक यूनिकोड वर्ण को एक से अधिक चार द्वारा दर्शाया जा सकता है। प्रत्येक चार के बजाय प्रत्येक यूनिकोड वर्ण के साथ काम करने के लिए System.Globalization.StringInfo वर्ग का उपयोग करें।


24

जैसा कि अन्य उत्तरों में बताया गया है, भले ही 3 दृश्यमान चरित्र हों, जिनका प्रतिनिधित्व 4 charवस्तुओं के साथ किया जाता है। जिसके कारण Length4 और 3 नहीं है।

MSDN कहता है कि

लंबाई की संपत्ति इस उदाहरण में चार वस्तुओं की संख्या लौटाती है, यूनिकोड वर्णों की संख्या नहीं।

हालाँकि यदि आप वास्तव में जानना चाहते हैं तो "पाठ तत्वों" की संख्या है न कि Charउन वस्तुओं की संख्या जिन्हें आप StringInfoकक्षा में उपयोग कर सकते हैं ।

var si = new StringInfo("A𠈓C");
Console.WriteLine(si.LengthInTextElements); // 3

आप इस तरह से प्रत्येक टेक्स्ट एलिमेंट की गणना कर सकते हैं

var enumerator = StringInfo.GetTextElementEnumerator("A𠈓C");
while(enumerator.MoveNext()){
    Console.WriteLine(enumerator.Current);
}

foreachस्ट्रिंग पर उपयोग करने से मध्य "पत्र" दो charवस्तुओं में विभाजित हो जाएगा और मुद्रित परिणाम स्ट्रिंग के अनुरूप नहीं होगा।


20

ऐसा इसलिए है क्योंकि Lengthसंपत्ति चार ऑब्जेक्ट्स की संख्या लौटाती है , यूनिकोड वर्णों की संख्या नहीं। आपके मामले में, यूनिकोड वर्णों में से एक का प्रतिनिधित्व एक से अधिक चार ऑब्जेक्ट (सरोगेटपेयर) द्वारा किया जाता है।

लंबाई की संपत्ति इस उदाहरण में चार वस्तुओं की संख्या लौटाती है, यूनिकोड वर्णों की संख्या नहीं। कारण यह है कि एक यूनिकोड वर्ण को एक से अधिक चार द्वारा दर्शाया जा सकता है। प्रत्येक चार के बजाय प्रत्येक यूनिकोड वर्ण के साथ काम करने के लिए System.Globalization.StringInfo वर्ग का उपयोग करें।


1
आपके पास इस उत्तर में "चरित्र" का अस्पष्ट उपयोग है। मेरा सुझाव है कि सटीक शब्दावली के साथ कम से कम पहले की जगह।
कक्षा

1
धन्यवाद। अस्पष्टता निश्चित की।
युवल इत्ज़चकोव

10

जैसा कि दूसरों ने कहा, यह स्ट्रिंग में वर्णों की संख्या नहीं है, बल्कि चार ऑब्जेक्ट्स की संख्या है। वर्ण code कोड बिंदु U + 20213 है। चूंकि मूल्य 16-बिट चार प्रकार की सीमा के बाहर है, इसलिए इसे सरोगेट जोड़ी के रूप में UTF-16 में एन्कोड किया गया है D840 DE13

वर्णों में लंबाई प्राप्त करने का तरीका अन्य उत्तरों में उल्लिखित था। हालांकि इसका उपयोग देखभाल के साथ किया जाना चाहिए क्योंकि यूनिकोड में एक चरित्र का प्रतिनिधित्व करने के कई तरीके हो सकते हैं। "आ" 1 रचित वर्ण या 2 वर्ण (a + diacritics) हो सकता है। ट्विटर के मामले में सामान्यीकरण की आवश्यकता हो सकती है ।

आपको यह पूरी तरह से कम से कम हर सॉफ्टवेयर डेवलपर को बिल्कुल पढ़ना चाहिए
, सकारात्मक रूप से यूनिकोड और चरित्र सेट (कोई फ़्यूज़!) के बारे में पता होना चाहिए!


6

ऐसा इसलिए है क्योंकि length()केवल यूनिकोड कोड बिंदुओं के लिए काम करता है जो इससे बड़ा नहीं हैं U+FFFF। कोड पॉइंट के इस सेट को बेसिक मल्टीलिंगुअल प्लेन के रूप में जाना जाता है (BMP) के और केवल 2 बाइट्स का उपयोग करता है।

BMPयूटीएफ -16 में यूनिकोड कोड पॉइंट्स को 4 बाइट सरोगेट जोड़े का उपयोग करके दर्शाया गया है।

वर्णों की संख्या (3) की सही गणना करने के लिए, उपयोग करें StringInfo

StringInfo b = new StringInfo("A𠈓C");
Console.WriteLine(string.Format("Length 2 = {0}", b.LengthInTextElements));

6

ठीक है, .Net और C # में सभी तार UTF-16LE के रूप में एन्कोड किए गए हैं । A stringको वर्णों के अनुक्रम के रूप में संग्रहीत किया जाता है। प्रत्येक char2 बाइट्स या 16 बिट्स के भंडारण को इनकैप्सुलेट करता है।

एक अक्षर, वर्ण, ग्लिफ़, प्रतीक, या विराम चिह्न के रूप में हम "कागज़ या स्क्रीन पर" जो देखते हैं, उसे एकल पाठ तत्व के रूप में सोचा जा सकता है। जैसा कि यूनिकोड मानक अनुलग्नक # 29 UNICODE TEXT SEGMENTATION में वर्णित है , प्रत्येक पाठ तत्व का प्रतिनिधित्व एक या अधिक कोड बिंदुओं द्वारा किया जाता है। कोड की एक विस्तृत सूची यहां पाई जा सकती है

प्रत्येक कोड प्वाइंट को कंप्यूटर द्वारा आंतरिक प्रतिनिधित्व के लिए बाइनरी में एन्कोड किया जाना चाहिए। जैसा कि कहा गया है, प्रत्येक char2 बाइट्स संग्रहीत करता है। या उससे नीचे के कोड पॉइंट्स को U+FFFFसिंगल में स्टोर किया जा सकता है char। ऊपर दिए गए कोड पॉइंट्स U+FFFFको एक सरोगेट जोड़ी के रूप में संग्रहीत किया जाता है, जो सिंगल कोड पॉइंट का प्रतिनिधित्व करने के लिए दो चार्ट का उपयोग करता है।

यह देखते हुए कि अब हम जानते हैं कि हम कटौती कर सकते हैं, एक पाठ तत्व charको दो वर्णों के सरोगेट जोड़ी के रूप में एक के रूप में संग्रहीत किया जा सकता है , या यदि पाठ तत्व का प्रतिनिधित्व कई कोड बिंदुओं द्वारा किया जाता है तो कुछ वर्णों और सरोगेट जोड़े के कुछ संयोजन। जैसे कि वे पर्याप्त जटिल नहीं थे, कुछ पाठ तत्वों को कोड पॉइंट्स के विभिन्न संयोजनों के रूप में वर्णित किया जा सकता है, जैसा कि यूनिकोड स्टैंडर्ड एनेक्स # 15, यूनीकोड ​​नॉर्मलाइज़ेशन फॉर्म्स


अन्तराल

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

आप .Net स्ट्रिंग्स को फिर से एनकोड कर सकते हैं। ताकि वे समान सामान्यीकरण फ़ॉर्म का उपयोग करें। एक बार सामान्य हो जाने पर, एक ही टेक्स्ट एलिमेंट्स के साथ दो स्ट्रिंग्स को एक ही तरह से इनकोड किया जाएगा। ऐसा करने के लिए, स्ट्रिंग का उपयोग करें। सामान्य कार्य करें। हालाँकि, याद रखें, कुछ अलग पाठ तत्व एक दूसरे के समान दिखते हैं। : -s


तो, सवाल के संबंध में इसका क्या मतलब है? टेक्स्ट एलिमेंट '𠈓'को सिंगल कोड पॉइंट U + 20213 cjk यूनिफाइड विचारधारा विस्तार b द्वारा दर्शाया गया है । इसका मतलब यह है कि इसे एक एकल के रूप में एन्कोड नहीं किया जा सकता है charऔर दो वर्णों का उपयोग करके सरोगेट जोड़ी के रूप में एन्कोड किया जाना चाहिए। यही कारण string bहै charकि अब एक है string a

यदि आपको मज़बूती से (कैविएट देखें) गिनती करने की आवश्यकता है, तो आपको इस तरह stringसे System.Globalization.StringInfoक्लास एलिमेंट्स की संख्या का उपयोग करना चाहिए ।

using System.Globalization;

string a = "abc";
string b = "A𠈓C";

Console.WriteLine("Length a = {0}", new StringInfo(a).LengthInTextElements);
Console.WriteLine("Length b = {0}", new StringInfo(b).LengthInTextElements);

उत्पादन दे रहा है,

"Length a = 3"
"Length b = 3"

जैसा सोचा था।


चेतावनी

यूनिकोड टेक्स्ट सेगमेंटेशन का .Net कार्यान्वयन इन StringInfoऔर TextElementEnumeratorक्लासेस आम तौर पर उपयोगी होना चाहिए और, ज्यादातर मामलों में, कॉल करने वाले से अपेक्षा होती है कि वह एक प्रतिक्रिया देगा। हालांकि, जैसा कि यूनिकोड स्टैंडर्ड एनेक्स # 29 में कहा गया है , "उपयोगकर्ता की धारणाओं के मिलान का लक्ष्य हमेशा सटीक रूप से पूरा नहीं किया जा सकता है क्योंकि अकेले पाठ में हमेशा सीमाओं को तय करने के लिए पर्याप्त जानकारी नहीं होती है।"


मुझे लगता है कि आपका उत्तर संभावित रूप से भ्रामक है। इस मामले में, this केवल एक एकल कोड बिंदु है, लेकिन चूंकि इसका कोड बिंदु 0xFFFF से अधिक है, इसलिए इसे सरोगेट जोड़ी का उपयोग करके 2 कोड इकाइयों के रूप में दर्शाया जाना चाहिए। ग्रैफेमी एक अन्य अवधारणा है जो कोड बिंदु के शीर्ष पर बनाई गई है, जहां एक ग्रेफेम को एकल कोड बिंदु या एकाधिक कोड बिंदुओं द्वारा दर्शाया जा सकता है, जैसा कि कोरियाई के हंगुल या कई लैटिन-आधारित भाषाओं में देखा गया है।
.तहठ

@nhahtdh, मैं सहमत हूं, मेरा जवाब गलत था। मैंने इसे फिर से लिखा है और उम्मीद है कि अब यह अधिक स्पष्टता पैदा करता है।
जॉडरेल
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.