Swift dizelerinde neden o‍👩‍👧‍👦 gibi emoji karakterlerine bu kadar garip davranılıyor?


540

👩‍👩‍👧‍👦 karakteri (iki kadın, bir kız ve bir erkek olan aile) şöyle kodlanır:

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

Yani çok ilginç bir şekilde kodlanmış; birim test için mükemmel hedef. Ancak Swift, nasıl tedavi edileceğini bilmiyor gibi görünüyor. Demek istediğim şu:

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

Swift, kendisini (iyi) ve bir çocuk (iyi!) İçerdiğini söylüyor. Ancak daha sonra bir kadın, kız veya sıfır genişlikli marangoz içermediğini söylüyor. Burada neler oluyor? Swift neden bir erkek içerdiğini biliyor ama bir kadın ya da kız değil? Tek bir karakter olarak muamele edip sadece kendisini içeren bir tanıma sahip olup olmadığını anlayabiliyordum, ancak bir alt bileşene sahip olması ve başkalarının beni şaşırtmaması gerçeği.

Böyle bir şey kullanırsam bu değişmez "👩".characters.first!.


Daha da kafa karıştırıcı bu:

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

ZWJ'leri oraya yerleştirmeme rağmen, karakter dizisine yansıtılmıyorlar. Takip eden şey biraz anlatıyordu:

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

Bu yüzden dizinin neye benzediğini bildiğim için, karakter dizisi ile aynı davranışı elde ediyorum.

Böyle bir şey kullanırsam bu da değişmez "👩".characters.first!.



1
Yorumlar uzun tartışmalar için değildir; bu sohbet sohbete taşındı .
Martijn Pieters

1
Swift 4'te düzeltildi. "👩‍👩‍👧‍👦".contains("\u{200D}")Hala yanlış döndürüyor, bunun bir hata mı yoksa özellik mi olduğundan emin değilim.
Kevin

4
Amanın. Unicode metni harap etti. Düz metni biçimlendirme diline dönüştürür.
Boann

6
@Boann evet ve hayır ... Hangul Jamo (255 kod noktası) gibi en / kod çözme şeyleri yapmak için Kanji (13.108 kod noktası) ve Çince İdeograflar (199.528 kod noktası) gibi bir kabus değil. Tabii ki, bir SO yorumunun izin verebileceğinden daha karmaşık ve ilginç, bu yüzden kendiniz kontrol etmenizi tavsiye ederim: D
Ben Leggiero

Yanıtlar:


402

Bu, Stringtürün Swift'te nasıl çalıştığı ve contains(_:)yöntemin nasıl çalıştığı ile ilgilidir.

'👩‍👩‍👧‍👦', bir dizede görünür bir karakter olarak gösterilen emoji dizisi olarak bilinen şeydir. Sekans Characternesnelerden oluşur ve aynı zamanda UnicodeScalarnesnelerden oluşur.

Dizenin karakter sayısını kontrol ederseniz, dört karakterden oluştuğunu görürsünüz, unicode skaler sayısını kontrol ederseniz, size farklı bir sonuç gösterir:

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

Şimdi, karakterleri ayrıştırır ve yazdırırsanız, normal karakterlere benzeyen şeyleri görürsünüz, ancak aslında ilk üç karakter hem emoji hem de sıfır genişlikli bir birleştirici içerir 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"]

Gördüğünüz gibi, yalnızca son karakter sıfır genişlikli bir birleştirici içermez, bu nedenle contains(_:)yöntemi kullanırken beklediğiniz gibi çalışır. Sıfır genişlikli birleştiriciler içeren emoji ile karşılaştırma yapmadığınız için, yöntem son karakterden başka bir eşleşme bulamaz.

Bunu genişletmek için, Stringsıfır genişlikli bir birleştirici ile biten bir emoji karakterinden oluşan bir contains(_:)yöntem oluşturursanız ve yönteme iletirseniz, bunu da değerlendirir false. Bu, verilen argümanla tam olarak eşleşmeye çalışan ile contains(_:)tam olarak aynı olmakla ilgilidir range(of:) != nil. Sıfır genişlikli bir birleştirici ile biten karakterler eksik bir dizi oluşturduğundan, yöntem, sıfır genişlikli birleştiricilerle biten karakterleri tam bir dizide birleştirirken argüman için bir eşleşme bulmaya çalışır. Bu, yöntemin aşağıdaki durumlarda hiç bir eşleşme bulamayacağı anlamına gelir:

  1. argüman sıfır genişlikli bir birleştirici ile sona erer ve
  2. ayrıştırılacak dize eksik bir dizi içermiyor (yani sıfır genişlikli bir birleştirici ile biten ve ardından uyumlu bir karakter içermeyen).

Göstermek:

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

Ancak, karşılaştırma yalnızca ileriye baktığından, dize içinde geriye doğru çalışarak birkaç tam diziyi daha bulabilirsiniz:

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

En kolay çözüm, range(of:options:range:locale:)yönteme özel bir karşılaştırma seçeneği sunmak olacaktır . Seçenek String.CompareOptions.literalkarşılaştırmayı tam karakter karakter denkliği üzerinde gerçekleştirir . Yan not olarak, burada karakter ile kastedilen Swift değil , Characterhem örneğin hem de karşılaştırma dizesinin UTF-16 temsili - ancak, Stringhatalı biçimlendirilmiş UTF-16'ya izin vermediğinden, bu aslında Unicode skalerlerin karşılaştırılmasına eşdeğerdir. temsilidir.

Burada Foundationyöntemi aşırı yükledim, bu yüzden orijinaline ihtiyacınız varsa, bunu veya bir şeyi yeniden adlandırın:

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

Şimdi yöntem, tamamlanmamış dizilerle bile her karakterle "olması gerektiği gibi" çalışır:

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

47
@MartinR akım UTR29 (Unicode 9.0) göre, bir uzatılmış sesletim küme ( GB10 ve GB11 kuralları ), ancak Swift açıkça eski bir sürümünü kullanıyor. Görünüşe göre bu, dilin 4. sürümü için bir hedeftir , bu nedenle bu davranış gelecekte değişecektir.
Michael Homer

9
@MichaelHomer: Görünüşe göre bu sabit olmuştur, "👩‍👩‍👧‍👦".countolarak değerlendirilir 1mevcut Xcode 9 beta ve Swift 4.
Martin R

5
Vay. Bu mükemmel. Ama şimdi dizelerle yaşadığım en kötü sorunun C veya Pascal tarzı kodlamaları kullanıp kullanmadığı eski günler için nostaljik hale geliyorum.
Owen Godfrey

2
Unicode standardının neden bunu desteklemesi gerekebileceğini anlıyorum, ama adamım, bu herhangi bir şey varsa, aşırı derecede karmaşık bir karmaşa: /
Monica

110

İlk sorun Vakıf ile contains(Swift's Stringa değil Collection) arasında köprü oluşturuyor olmanızdır , bu yüzden bu, NSStringEmoji'nin oluşturduğu kulpları Swift kadar güçlü bir şekilde ele almadığına inanmıyorum. Bununla birlikte, Swift, şu anda Unicode 8'i uyguladığına inanıyorum, bu da Unicode 10'daki bu durumun revize edilmesine ihtiyaç duyuyordu (bu nedenle, Unicode 10'u uyguladıklarında tüm bunlar değişebilir;

Bir şeyi basitleştirmek için, Vakıf'tan kurtulalım ve daha açık görünümler sağlayan Swift'i kullanalım. Karakterlerle başlayacağız:

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

TAMAM. Beklediğimiz bu. Ama bu bir yalan. Bu karakterlerin gerçekte ne olduğunu görelim.

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

Ah… Öyleyse öyle ["👩ZWJ", "👩ZWJ", "👧ZWJ", "👦"]. Bu her şeyi biraz daha netleştiriyor. 👩 bu listenin bir üyesi değil ("👩ZWJ"), ancak 👦 bir üyedir.

Sorun şu ki Character, şeyleri bir araya getiren bir "grafik kümesi" dir (ZWJ'yi bağlamak gibi). Gerçekten aradığınız şey, bir unicode skalerdir. Ve bu tam olarak beklediğiniz gibi çalışır:

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

Ve elbette orada olan gerçek karakteri de arayabiliriz:

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

(Bu, Ben Leggiero'nun puanlarını ağır bir şekilde çoğaltır. Bunu cevapladığını fark etmeden önce gönderdim. Herkes için daha açık olması durumunda ayrılmak.)


Wth ne anlama geliyor ZWJ?
LinusGeffarth

2
Sıfır Genişlik Marangoz
Rob Napier

Swift 4'teki @RobNapier'in Stringbir koleksiyon türüne geri döndüğü iddia edildi. Bu, cevabınızı hiç etkiliyor mu?
Ben Leggiero

Hayır. Bu sadece abonelik gibi şeyleri değiştirdi. Karakterlerin çalışma şeklini değiştirmedi.
Rob Napier

75

Görünüşe göre Swift, ZWJa'nın hemen önündeki karakterle genişletilmiş bir grafik kümesi kümesi olduğunu düşünüyor. Karakter dizisini şu karakterlerle eşlerken bunu görebiliriz unicodeScalars:

Array(manual.characters).map { $0.description.unicodeScalars }

Bu, LLDB'den aşağıdakileri yazdırır:

4 elements
  ▿ 0 : StringUnicodeScalarView("👩‍")
    - 0 : "\u{0001F469}"
    - 1 : "\u{200D}"1 : StringUnicodeScalarView("👩‍")
    - 0 : "\u{0001F469}"
    - 1 : "\u{200D}"2 : StringUnicodeScalarView("👧‍")
    - 0 : "\u{0001F467}"
    - 1 : "\u{200D}"3 : StringUnicodeScalarView("👦")
    - 0 : "\u{0001F466}"

Ayrıca, .containsgenişletilmiş grafik kümelerini tek bir karakterde gruplar. Örneğin, hangul karakterleri alarak , ve ( "one" için Korece kelimeyi yapmak için birleştirir: 한):

"\u{1112}\u{1161}\u{11AB}".contains("\u{1112}") // false

Bu , üç kod noktasının, bir karakter gibi davranan bir kümede gruplandığı için bulamadı . Benzer şekilde, \u{1F469}\u{200D}( WOMAN ZWJ) bir karakter gibi davranan bir kümedir.


19

Diğer cevaplar Swift'in ne yaptığını tartışıyor, ancak neden hakkında fazla ayrıntıya girmiyor.

“Å” nin “Å” e eşit olmasını mı bekliyorsunuz? Yapacağını umuyorum.

Bunlardan biri birleştiricili bir harf, diğeri tek bir bestedir. Bir temel karaktere birçok farklı birleştirici ekleyebilirsiniz ve bir insan hala tek bir karakter olduğunu düşünür. Bu tür bir tutarsızlıkla başa çıkmak için, kullanılan kod noktalarına bakılmaksızın bir insanın bir karakteri neyi dikkate alacağını temsil etmek için bir grafik kavramı yaratılmıştır.

Metin mesajlaşma hizmetleri, karakterleri yıllardır grafiksel emojilerle birleştiriyor :) →  🙂. Böylece Unicode'a çeşitli emojiler eklendi.
Bu hizmetler ayrıca emojiyi bir araya getirerek kompozit emojiye dönüştürmeye başladı.
Elbette tüm olası kombinasyonları ayrı kod noktalarına kodlamanın makul bir yolu yoktur, bu nedenle Unicode Konsorsiyumu bu bileşik karakterleri kapsayacak şekilde grafik kavramları kavramını genişletmeye karar verdi.

"👩‍👩‍👧‍👦"Swift'in varsayılan olarak yaptığı gibi, bununla birlikte grafik düzeyinde çalışmayı denerseniz , bunun kaynaması tek bir "grafik kümesi" olarak düşünülmelidir.

Bunun "👦"bir parçası olarak içerip içermediğini kontrol etmek istiyorsanız , daha düşük bir seviyeye inmelisiniz.


Swift sözdizimini bilmiyorum, bu yüzden burada Unicode için benzer düzeyde desteğe sahip bazı Perl 6 var.
(Perl 6, Unicode sürüm 9'u destekler, bu nedenle tutarsızlıklar olabilir)

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

Bir seviyeye inelim

# 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

Bu seviyeye inmek bazı şeyleri daha da zorlaştırabilir.

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

.containsSwift'te bunu kolaylaştırdığını varsayıyorum , ancak bu daha zor hale gelen başka şeyler olmadığı anlamına gelmez.

Bu seviyede çalışmak, örneğin bir dizginin ortasında yanlışlıkla bir dizeyi bölmeyi çok daha kolay hale getirir.


Yanlışlıkla sorduğunuz şey, bu üst düzey temsilin neden daha düşük düzeyli bir temsil gibi çalışmadığıdır. Cevap tabii ki öyle değil.

Kendinize “ bunun neden bu kadar karmaşık olması gerektiğini ” soruyorsanız , cevap elbette “ insanlar ” dır .


4
Beni son örnek hattında kaybettin; burada ne yapmalı rotorve grepyapmalı? Peki nedir 1-$l?
Ben Leggiero

4
"Grafik" terimi en az 50 yaşındadır. Unicode bunu standarda tanıttı çünkü zaten karakter olarak düşündüğünden oldukça farklı bir şey ifade etmek için "karakter" terimini kullanmışlardı. Yazdıklarınızı bununla tutarlı olarak okuyabilirim, ancak başkalarının yanlış izlenime sahip olabileceğinden şüpheleniyorum, bu yüzden bu (umarım açıklayıcı) yorum.
raiph

2
Önce BenLeggiero rotor,. Kod say (1,2,3,4,5,6).rotor(3)verir ((1 2 3) (4 5 6)). Bu, her uzunluktaki listelerin bir listesi 3. say (1,2,3,4,5,6).rotor(3=>-2)ikinci alt liste 2yerine 4üçüncü ile başlar 3ve bu şekilde devam eder ((1 2 3) (2 3 4) (3 4 5) (4 5 6)). Eğer @matchiçeriyorsa "👩‍👩‍👧‍👦".ords@ Brad'in kodu sadece bir alt liste oluşturur, bu yüzden =>1-$lbit önemsizdir (kullanılmamış). Sadece @matchdaha kısa olması durumunda geçerlidir @components.
raiph

1
grepinvokantındaki her öğeyi eşleştirmeye çalışır (bu durumda, alt listelerinin listesi @components). Her öğeyi eşleştirici argümanıyla eşleştirmeye çalışır (bu durumda @match). .BoolDaha sonra döner TrueIFF grepen az bir maç üretir.
raiph

18

Swift 4.0 güncellemesi

Dize, SE-0163'te belgelendiği gibi Swift 4 güncellemesinde birçok revizyon aldı . Bu demo için iki farklı yapıyı temsil eden iki emoji kullanılmıştır. Her ikisi de bir emoji dizisi ile birleştirilir.

👍🏽iki emojinin birleşimidir 👍ve🏽

👩‍👩‍👧‍👦sıfır genişlikli mafsal bağlı dört emoji kombinasyonudur. Biçim👩‍joiner👩‍joiner👧‍joiner👦

1. Sayımlar

Swift 4.0'da emoji, grafik kümesi olarak sayılır. Her bir emoji 1 olarak sayılır. countÖzellik doğrudan dize için de kullanılabilir. Böylece doğrudan böyle diyebilirsiniz.

"👍🏽".count  // 1. Not available on swift 3
"👩‍👩‍👧‍👦".count  // 1. Not available on swift 3

Bir dizenin karakter dizisi de Swift 4.0'da grafik kümeleri olarak sayılır, bu nedenle aşağıdaki kodların her ikisi de yazdırılır. Bu iki emoji, birkaç emojinin aralarında sıfır genişlikli birleştirici ile veya bunlar olmadan birleştirildiği emoji dizilerinin örnekleridir \u{200d}. Hızlı 3.0'da, bu dizenin karakter dizisi her emojiyi ayırır ve birden çok öğeye (emoji) sahip bir dizi ile sonuçlanır. Bu işlemde marangoz ihmal edilir. Ancak, Swift 4.0'da karakter dizisi tüm emojileri tek parça olarak görür. Böylece herhangi bir emoji her zaman 1 olacaktır.

"👍🏽".characters.count  // 1. In swift 3, this prints 2
"👩‍👩‍👧‍👦".characters.count  // 1. In swift 3, this prints 4

unicodeScalars Swift 4'te değişmeden kalır. Verilen dizede benzersiz Unicode karakterleri sağlar.

"👍🏽".unicodeScalars.count  // 2. Combination of two emoji
"👩‍👩‍👧‍👦".unicodeScalars.count  // 7. Combination of four emoji with joiner between them

2. İçerir

Swift 4.0'da, containsyöntem emoji'de sıfır genişlikli birleştiriciyi yok sayar. Böylece, dört emoji bileşeninden herhangi biri için true değerini döndürür ve joiner'ı "👩‍👩‍👧‍👦"kontrol ederseniz false değerini döndürür. Bununla birlikte, Swift 3.0'da, marangoz göz ardı edilmez ve önündeki emoji ile birleştirilir. "👩‍👩‍👧‍👦"İlk üç bileşenli emojiyi içerip içermediğini kontrol ettiğinizde sonuç yanlış olur

"👍🏽".contains("👍")       // true
"👍🏽".contains("🏽")        // true
"👩‍👩‍👧‍👦".contains("👩‍👩‍👧‍👦")       // true
"👩‍👩‍👧‍👦".contains("👩")       // true. In swift 3, this prints false
"👩‍👩‍👧‍👦".contains("\u{200D}") // false
"👩‍👩‍👧‍👦".contains("👧")       // true. In swift 3, this prints false
"👩‍👩‍👧‍👦".contains("👦")       // true

0

Emojiler, unicode standardı gibi, aldatıcı bir şekilde karmaşıktır. Cilt tonları, cinsiyetler, işler, insan grupları, sıfır genişlikli marangoz dizileri, bayraklar (2 karakterli unicode) ve diğer komplikasyonlar emojiyi ayrıştırabilir. Bir Noel Ağacı, Bir Dilim Pizza veya Bir Kaka Yığını tek bir Unicode kod noktasıyla temsil edilebilir. Yeni emojiler tanıtıldığında, iOS desteği ile emoji sürümü arasında bir gecikme olduğunu belirtmiyoruz. Bu ve iOS'un farklı sürümlerinin unicode standardının farklı sürümlerini desteklemesi gerçeği.

TL; DR. Ben bu özellikler üzerinde çalışmış ve ben yazarım kütüphane kaynaklı açılan JKEmoji emoji'yi ile yardım ayrıştırma dizelerine. Ayrıştırmayı şu kadar kolaylaştırır:

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

5

Bunu, en son unicode sürümünden ( son zamanlarda 12.0 ) tanınan tüm emojilerin yerel veritabanını rutin olarak yenileyerek ve çalışan bit sürümünde bit eşlem temsiline bakarak geçerli bir emoji olarak tanınan şeyle çapraz referans alarak yapar. tanınmayan bir emoji karakteri.

NOT

Yazar olduğumu açıkça belirtmeden kütüphanemin reklamının yapılması için önceki bir yanıt silindi. Bunu tekrar kabul ediyorum.


2
Kütüphanenizden etkilendim ve bunun genellikle eldeki konu ile nasıl ilişkili olduğunu görüyorum, bunun doğrudan soru ile nasıl ilişkili olduğunu
görmüyorum
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.