swift - क्यों स्विफ्ट स्ट्रिंग्स में इमोजी कैरेक्टर जैसे अजीब व्यवहार किए गए हैं?




string unicode (4)

अन्य उत्तर चर्चा करते हैं कि स्विफ्ट क्या करता है, लेकिन इसके बारे में अधिक विस्तार में नहीं जाना चाहिए।

क्या आप उम्मीद करते हैं कि “to” की बराबरी “Å” से होगी? मुझे उम्मीद है कि आप करेंगे।

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

अब टेक्स्ट मैसेजिंग सेवाएं सालों से चरित्रों को ग्राफिकल इमोजी में मिला रही हैं :)🙂 । इसलिए यूनिकोड में विभिन्न इमोजी जोड़े गए।
इन सेवाओं ने इमोजी को एक साथ मिश्रित इमोजी में जोड़ना शुरू किया।
निश्चित रूप से सभी संभावित संयोजनों को व्यक्तिगत कोडपॉइंट्स में एन्कोड करने का कोई उचित तरीका नहीं है, इसलिए यूनिकोड कंसोर्टियम ने इन समग्र वर्णों को शामिल करने के लिए अंगूर की अवधारणा पर विस्तार करने का निर्णय लिया।

अगर यह उबलता है तो "👩‍👩‍👧‍👦" को एक "👩‍👩‍👧‍👦" क्लस्टर" के रूप में माना जाना चाहिए यदि आप इसे ग्रैफेमी स्तर पर काम करने की कोशिश कर रहे हैं, जैसा कि स्विफ्ट डिफ़ॉल्ट रूप से करता है।

यदि आप जांचना चाहते हैं कि इसमें "👦" शामिल है या नहीं, तो आपको निम्न स्तर पर जाना चाहिए।

मैं स्विफ्ट सिंटैक्स नहीं जानता, इसलिए यहां कुछ पर्ल 6 है जो यूनिकोड के लिए समान स्तर का समर्थन करता है।
(पर्ल 6 यूनिकोड संस्करण 9 का समर्थन करता है ताकि विसंगतियां हो सकती हैं)

say "\c[family: woman woman girl boy]" eq "👩‍👩‍👧‍👦"; # True

# .contains is a Str method only, in Perl 6
say "👩‍👩‍👧‍👦".contains("👩‍👩‍👧‍👦")    # True
say "👩‍👩‍👧‍👦".contains("👦");        # False
say "👩‍👩‍👧‍👦".contains("\x[200D]");  # False

# comb with no arguments splits a Str into graphemes
my @graphemes = "👩‍👩‍👧‍👦".comb;
say @graphemes.elems;                # 1

एक स्तर नीचे चला गया

# look at it as a list of NFC codepoints
my @components := "👩‍👩‍👧‍👦".NFC;
say @components.elems;                     # 7

say @components.grep("👦".ord).Bool;       # True
say @components.grep("\x[200D]".ord).Bool; # True
say @components.grep(0x200D).Bool;         # True

इस स्तर तक नीचे जाने से हालांकि कुछ चीजें कठिन हो सकती हैं।

my @match = "👩‍👩‍👧‍👦".ords;
my $l = @match.elems;
say @components.rotor( $l => 1-$l ).grep(@match).Bool; # True

मुझे लगता है कि। स्विफ्ट में .contains यह आसान बनाता है, लेकिन इसका मतलब यह नहीं है कि अन्य चीजें नहीं हैं जो अधिक कठिन हो जाती हैं।

इस स्तर पर काम करना उदाहरण के लिए गलती से एक स्ट्रिंग को संयुक्त चरित्र के बीच में विभाजित करना आसान बनाता है।

आप अनजाने में क्या पूछ रहे हैं कि यह उच्च स्तर का प्रतिनिधित्व निचले स्तर के प्रतिनिधित्व की तरह काम क्यों नहीं करता है। जवाब बेशक है, यह नहीं माना जाता है।

यदि आप अपने आप से पूछ रहे हैं " यह इतना जटिल क्यों है ", तो इसका जवाब निश्चित रूप से " मनुष्य " है।

चरित्र 👩 two👧👧👦👦 (दो महिलाओं, एक लड़की और एक लड़के के साथ परिवार) इस तरह से इनकोड किया गया है:

U+1F469 WOMAN ,
‍U+200D ZWJ ,
U+1F469 WOMAN ,
U+200D ZWJ ,
U+1F467 GIRL ,
U+200D ZWJ ,
U+1F466 BOY

तो यह बहुत दिलचस्प है-एन्कोडेड; एक इकाई परीक्षण के लिए सही लक्ष्य। हालांकि, स्विफ्ट को यह पता नहीं लगता है कि इसका इलाज कैसे किया जाता है। यहाँ मेरा मतलब है:

"👩‍👩‍👧‍👦".contains("👩‍👩‍👧‍👦") // true
"👩‍👩‍👧‍👦".contains("👩") // false
"👩‍👩‍👧‍👦".contains("\u{200D}") // false
"👩‍👩‍👧‍👦".contains("👧") // false
"👩‍👩‍👧‍👦".contains("👦") // true

तो, स्विफ्ट का कहना है कि इसमें खुद (अच्छा) और एक लड़का (अच्छा!) शामिल है। लेकिन यह तब कहता है कि इसमें एक महिला, लड़की या शून्य-चौड़ाई वाले शामिल नहीं हैं। यहाँ क्या हो रहा है? स्विफ्ट को क्यों पता है कि इसमें एक लड़का शामिल है लेकिन एक महिला या लड़की नहीं है? मैं समझ सकता था कि क्या यह एक एकल चरित्र के रूप में माना जाता है और केवल इसे ही मान्यता देता है, लेकिन तथ्य यह है कि इसे एक उपसमुच्चय मिला और कोई अन्य मुझे चकित नहीं करता।

अगर मैं "👩".characters.first! जैसी किसी चीज़ का उपयोग करता हूं तो यह नहीं बदलता है "👩".characters.first!

इससे भी अधिक उलझन यह है:

let manual = "\u{1F469}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}"
Array(manual.characters) // ["👩‍", "👩‍", "👧‍", "👦"]

हालाँकि मैंने ZWJ को वहाँ रखा था, वे वर्ण सरणी में परिलक्षित नहीं होते हैं। इसके बाद क्या हुआ थोड़ा बता रहा है:

manual.contains("👩") // false
manual.contains("👧") // false
manual.contains("👦") // true

इसलिए मुझे चरित्र सरणी के साथ एक ही व्यवहार मिलता है ... जो कि बहुत कष्टप्रद है, क्योंकि मुझे पता है कि सरणी कैसी दिखती है।

यह भी अगर मैं "👩".characters.first! तरह कुछ का उपयोग नहीं बदलता है "👩".characters.first!


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

टी एल; डॉ। मैंने इन विशेषताओं पर काम किया है और एक लाइब्रेरी खोली है जिसमें मैं JKEmoji लिए लेखक हूं, जो JKEmoji साथ तार को JKEmoji में मदद करता है। यह पार्सिंग को आसान बनाता है:

print("I love these emojis 👩‍👩‍👧‍👦💪🏾🧥👧🏿🌈".emojiCount)

5

ऐसा लगता है कि नियमित रूप से नवीनतम यूनिकोड संस्करण (हाल ही में 12.0 ) के रूप में सभी मान्यता प्राप्त इमोजी के एक स्थानीय डेटाबेस को ताज़ा करके और बिटमैप प्रतिनिधित्व को देखते हुए चल रहे ओएस संस्करण में एक मान्य इमोजी के रूप में मान्यता प्राप्त है। एक अपरिचित इमोजी चरित्र।

ध्यान दें

मेरे पुस्तकालय के विज्ञापन के लिए एक पिछला उत्तर स्पष्ट रूप से यह बताए बिना हटा दिया गया कि मैं लेखक हूं। मैं इसे फिर से स्वीकार कर रहा हूं।


पहली समस्या यह है कि आप फाउंडेशन के साथ contains (स्विफ्ट का String एक Collection नहीं है), इसलिए यह NSString व्यवहार है, जो मुझे नहीं लगता कि स्विफ्ट के रूप में इमोजी के रूप में शक्तिशाली रूप से बनाए गए हैंडल हैं। उस ने कहा, स्विफ्ट मेरा मानना ​​है कि अभी यूनिकोड 8 को लागू कर रहा है, जिसे यूनिकोड 10 में इस स्थिति के आसपास भी संशोधन की आवश्यकता है (इसलिए जब वे यूनिकोड 10 को लागू करते हैं, तो यह सब बदल सकता है; मैंने इसमें नहीं खोदा है या नहीं)

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

"👩‍👩‍👧‍👦".characters.forEach { print($0) }
👩‍
👩‍
👧‍
👦

ठीक। यही हमें उम्मीद थी। लेकिन यह झूठ है। आइए देखें कि वे पात्र वास्तव में क्या हैं।

"👩‍👩‍👧‍👦".characters.forEach { print(String($0).unicodeScalars.map{$0}) }
["\u{0001F469}", "\u{200D}"]
["\u{0001F469}", "\u{200D}"]
["\u{0001F467}", "\u{200D}"]
["\u{0001F466}"]

आह ... तो यह ["👩ZWJ", "👩ZWJ", "👧ZWJ", "👦"] । यह सब कुछ थोड़ा और स्पष्ट करता है। (इस सूची का सदस्य नहीं है (यह "WZWJ") है, लेकिन member सदस्य है।

समस्या यह है कि Character एक "ग्रैफेम क्लस्टर" है, जो एक साथ चीजों की रचना करता है (जैसे जेडडब्ल्यूजे संलग्न करना)। क्या आप वास्तव में खोज रहे हैं एक यूनिकोड स्केलर है। और यह ठीक वैसा ही काम करता है जैसा आप उम्मीद कर रहे हैं:

"👩‍👩‍👧‍👦".unicodeScalars.contains("👩") // true
"👩‍👩‍👧‍👦".unicodeScalars.contains("\u{200D}") // true
"👩‍👩‍👧‍👦".unicodeScalars.contains("👧") // true
"👩‍👩‍👧‍👦".unicodeScalars.contains("👦") // true

और निश्चित रूप से हम वहां मौजूद वास्तविक चरित्र की तलाश कर सकते हैं:

"👩‍👩‍👧‍👦".characters.contains("👩\u{200D}") // true

(यह बेन लेगिएरो के बिंदुओं पर भारी नक़ल करता है। मैंने उत्तर देने से पहले यह पोस्ट किया था कि वह जवाब दे। किसी के लिए यह स्पष्ट नहीं है।)


यह स्विफ्ट प्रकार स्विफ्ट में कैसे काम करता है और कैसे contains(_:) विधि काम करता है के साथ क्या करना है।

The 'known'👧👧👦👦 ’एक इमोजी अनुक्रम के रूप में जाना जाता है, जिसे एक स्ट्रिंग में एक दृश्यमान चरित्र के रूप में प्रस्तुत किया गया है। अनुक्रम Character वस्तुओं से बना है, और एक ही समय में यह UnicodeScalar वस्तुओं से बना है।

यदि आप स्ट्रिंग के कैरेक्टर काउंट की जाँच करते हैं, तो आप देखेंगे कि यह चार अक्षरों से बना है, जबकि यदि आप यूनिकोड स्केलर काउंट की जाँच करते हैं, तो यह आपको एक अलग परिणाम दिखाएगा:

print("👩‍👩‍👧‍👦".characters.count)     // 4
print("👩‍👩‍👧‍👦".unicodeScalars.count) // 7

अब, यदि आप पात्रों के माध्यम से पार्स करते हैं और उन्हें प्रिंट करते हैं, तो आप देखेंगे कि सामान्य पात्रों की तरह क्या लगता है, लेकिन वास्तव में तीन पहले पात्रों में एक इमोजी और साथ ही साथ उनके UnicodeScalarView में एक शून्य-चौड़ाई वाले योजक UnicodeScalarView :

for char in "👩‍👩‍👧‍👦".characters {
    print(char)

    let scalars = String(char).unicodeScalars.map({ String($0.value, radix: 16) })
    print(scalars)
}

// 👩‍
// ["1f469", "200d"]
// 👩‍
// ["1f469", "200d"]
// 👧‍
// ["1f467", "200d"]
// 👦
// ["1f466"]

जैसा कि आप देख सकते हैं, केवल अंतिम वर्ण में शून्य-चौड़ाई वाले योजक नहीं होते हैं, इसलिए जब contains(_:) विधि का उपयोग किया जाता है, तो यह काम करता है जैसा कि आप अपेक्षा करेंगे। चूंकि आप शून्य-चौड़ाई वाले जॉइनरों वाले इमोजी के खिलाफ तुलना नहीं कर रहे हैं, इसलिए विधि किसी भी अंतिम वर्ण के लिए मैच नहीं ढूंढेगी।

इस पर विस्तार करने के लिए, यदि आप एक String बनाते हैं जो शून्य-चौड़ाई वाले योजक के साथ समाप्त होने वाले इमोजी वर्ण से बना होता है, और इसे contains(_:) विधि से पास करता है, तो यह false मूल्यांकन भी करेगा। contains(_:) range(of:) != nil समान सटीक होना range(of:) != nil , जो दिए गए तर्क का सटीक मिलान खोजने की कोशिश करता है। चूंकि शून्य-चौड़ाई वाले योजक के साथ समाप्त होने वाले वर्ण एक अपूर्ण अनुक्रम बनाते हैं, इसलिए विधि एक पूर्ण-अनुक्रम में शून्य-चौड़ाई वाले योजक के साथ समाप्त होने वाले वर्णों को मिलाते हुए तर्क के लिए एक मैच खोजने की कोशिश करती है। इसका मतलब है कि विधि कभी भी एक मैच नहीं पाएगी यदि:

  1. तर्क शून्य-चौड़ाई वाले योजक के साथ समाप्त होता है, और
  2. पार्स करने के लिए स्ट्रिंग में एक अधूरा अनुक्रम शामिल नहीं है (यानी एक शून्य-चौड़ाई वाले योजक के साथ समाप्त होने और एक संगत चरित्र के बाद नहीं)।

प्रदर्शित करना:

let s = "\u{1f469}\u{200d}\u{1f469}\u{200d}\u{1f467}\u{200d}\u{1f466}" // 👩‍👩‍👧‍👦

s.range(of: "\u{1f469}\u{200d}") != nil                            // false
s.range(of: "\u{1f469}\u{200d}\u{1f469}") != nil                   // false

हालाँकि, चूंकि तुलना केवल आगे लगती है, आप पीछे की ओर काम करके स्ट्रिंग के भीतर कई अन्य पूर्ण अनुक्रम पा सकते हैं:

s.range(of: "\u{1f466}") != nil                                    // true
s.range(of: "\u{1f467}\u{200d}\u{1f466}") != nil                   // true
s.range(of: "\u{1f469}\u{200d}\u{1f467}\u{200d}\u{1f466}") != nil  // true

// Same as the above:
s.contains("\u{1f469}\u{200d}\u{1f467}\u{200d}\u{1f466}")          // true

सबसे आसान समाधान range(of:options:range:locale:) विधि के लिए एक विशिष्ट तुलना विकल्प प्रदान करना होगा। विकल्प String.CompareOptions.literal एक सटीक चरित्र-दर-वर्ण समानता पर तुलना करता है। एक साइड नोट के रूप में, यहां चरित्र का क्या मतलब है, स्विफ्ट Character , लेकिन UTF-16 दोनों उदाहरण और तुलना स्ट्रिंग का प्रतिनिधित्व करता है - हालांकि, चूंकि String UTF-16 विकृत नहीं होने देता है, यह अनिवार्य रूप से तुलना करने के लिए बराबर है। यूनिकोड स्केलर प्रतिनिधित्व।

यहां मैंने Foundation विधि को ओवरलोड किया है, इसलिए यदि आपको मूल एक की आवश्यकता है, तो इस एक या कुछ का नाम बदलें:

extension String {
    func contains(_ string: String) -> Bool {
        return self.range(of: string, options: String.CompareOptions.literal) != nil
    }
}

अब यह विधि अधूरे क्रमों के साथ भी प्रत्येक वर्ण के साथ "होनी चाहिए":

s.contains("👩")          // true
s.contains("👩\u{200d}")  // true
s.contains("\u{200d}")    // true





emoji