Mengapa karakter emoji seperti πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘¦ diperlakukan begitu aneh di string Swift?

540

Karakter πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘¦ (keluarga dengan dua wanita, satu perempuan, dan satu laki-laki) dikodekan sebagai berikut:

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

Jadi itu sangat menarik dikodekan; target sempurna untuk unit test. Namun, Swift sepertinya tidak tahu bagaimana mengobatinya. Inilah yang saya maksud:

"πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘¦".contains("πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘¦") // true
"πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘¦".contains("πŸ‘©") // false
"πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘¦".contains("\u{200D}") // false
"πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘¦".contains("πŸ‘§") // false
"πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘¦".contains("πŸ‘¦") // true

Jadi, Swift mengatakan itu berisi dirinya sendiri (baik) dan anak laki-laki (baik!). Tetapi dikatakan bahwa itu tidak mengandung seorang wanita, gadis, atau joiner dengan lebar nol. Apa yang sedang terjadi disini? Mengapa Swift tahu itu mengandung anak laki-laki tetapi bukan perempuan atau perempuan? Saya bisa mengerti jika itu diperlakukan sebagai karakter tunggal dan hanya mengenalinya mengandung dirinya sendiri, tetapi fakta bahwa ia mendapat satu subkomponen dan tidak ada yang lain membuatku bingung.

Ini tidak berubah jika saya menggunakan sesuatu seperti "πŸ‘©".characters.first!.


Yang lebih membingungkan adalah ini:

let manual = "\u{1F469}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}"
Array(manual.characters) // ["πŸ‘©β€", "πŸ‘©β€", "πŸ‘§β€", "πŸ‘¦"]

Meskipun saya menempatkan ZWJ di sana, mereka tidak tercermin dalam array karakter. Yang terjadi selanjutnya sedikit memberi tahu:

manual.contains("πŸ‘©") // false
manual.contains("πŸ‘§") // false
manual.contains("πŸ‘¦") // true

Jadi saya mendapatkan perilaku yang sama dengan array karakter ... yang sangat menjengkelkan, karena saya tahu seperti apa array itu.

Ini juga tidak berubah jika saya menggunakan sesuatu seperti "πŸ‘©".characters.first!.

Ben Leggiero
sumber
1
Komentar bukan untuk diskusi panjang; percakapan ini telah dipindahkan ke obrolan .
Martijn Pieters
1
Diperbaiki dalam Swift 4. "πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘¦".contains("\u{200D}")masih mengembalikan false, tidak yakin apakah itu bug atau fitur.
Kevin
4
Astaga. Unicode telah merusak teks. Ini mengubah teks biasa menjadi bahasa markup.
Boann
6
@Boann ya dan tidak ... banyak dari perubahan ini dilakukan untuk membuat en / decoding hal-hal seperti Hangul Jamo (255 codepoint) bukan mimpi buruk absolut seperti bagi Kanji (13.108 codepoint) dan Ideograf Cina (199.528 codepoint). Tentu saja, ini lebih rumit dan menarik daripada yang bisa diberikan oleh komentar SO, jadi saya sarankan Anda untuk memeriksanya sendiri: D
Ben Leggiero

Jawaban:

402

Ini ada hubungannya dengan bagaimana Stringtipe ini bekerja di Swift, dan bagaimana contains(_:)metode ini bekerja.

'πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘¦' adalah apa yang dikenal sebagai urutan emoji, yang diterjemahkan sebagai satu karakter yang terlihat dalam sebuah string. Urutan terdiri dari Characterobjek, dan pada saat yang sama terdiri dari UnicodeScalarobjek.

Jika Anda memeriksa jumlah karakter string, Anda akan melihat bahwa itu terdiri dari empat karakter, sedangkan jika Anda memeriksa jumlah skalar unicode, itu akan menunjukkan kepada Anda hasil yang berbeda:

print("πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘¦".characters.count)     // 4
print("πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘¦".unicodeScalars.count) // 7

Sekarang, jika Anda mem-parsing melalui karakter dan mencetaknya, Anda akan melihat apa yang tampak seperti karakter normal, tetapi sebenarnya tiga karakter pertama berisi emoji serta joiner lebar nol di dalamnya 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"]

Seperti yang Anda lihat, hanya karakter terakhir yang tidak mengandung joiner lebar nol, jadi ketika menggunakan contains(_:)metode ini, ia berfungsi seperti yang Anda harapkan. Karena Anda tidak membandingkan emoji yang mengandung penggabung lebar nol, metode ini tidak akan menemukan kecocokan untuk apa pun kecuali karakter terakhir.

Untuk memperluas ini, jika Anda membuat Stringyang terdiri dari karakter emoji yang diakhiri dengan penggabung lebar nol, dan meneruskannya ke contains(_:)metode, itu juga akan dievaluasi false. Ini berkaitan dengan contains(_:)persis sama dengan range(of:) != nil, yang mencoba menemukan kecocokan yang tepat dengan argumen yang diberikan. Karena karakter yang diakhiri dengan joiner lebar nol membentuk urutan yang tidak lengkap, metode ini mencoba untuk menemukan kecocokan untuk argumen sambil menggabungkan karakter yang diakhiri dengan joiner lebar nol ke dalam urutan yang lengkap. Ini berarti bahwa metode ini tidak akan pernah menemukan kecocokan jika:

  1. argumen berakhir dengan joiner lebar nol, dan
  2. string ke parse tidak mengandung urutan yang tidak lengkap (yaitu diakhiri dengan joiner lebar nol dan tidak diikuti oleh karakter yang kompatibel).

Untuk menunjukkan:

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

Namun, karena perbandingan hanya melihat ke depan, Anda dapat menemukan beberapa urutan lengkap lainnya dalam string dengan bekerja mundur:

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

Solusi termudah adalah dengan memberikan opsi perbandingan spesifik untuk range(of:options:range:locale:)metode ini. Opsi String.CompareOptions.literalmelakukan perbandingan pada kesetaraan karakter per karakter yang tepat . Sebagai catatan, apa yang dimaksud dengan karakter di sini bukanlah Swift Character, tetapi representasi UTF-16 dari instance dan string perbandingan - namun, karena Stringtidak mengizinkan UTF-16 cacat, ini pada dasarnya setara dengan membandingkan skalar Unicode perwakilan.

Di sini saya telah membebani Foundationmetode ini, jadi jika Anda membutuhkan yang asli, ganti nama ini atau apalah:

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

Sekarang metode ini berfungsi sebagaimana mestinya "harus" dengan setiap karakter, bahkan dengan urutan yang tidak lengkap:

s.contains("πŸ‘©")          // true
s.contains("πŸ‘©\u{200d}")  // true
s.contains("\u{200d}")    // true
xoudini
sumber
47
@ MartinR Menurut UTR29 saat ini (Unicode 9.0), ini adalah cluster grapheme yang diperluas ( aturan GB10 dan GB11 ), tetapi Swift jelas menggunakan versi yang lebih lama. Rupanya memperbaiki itu adalah tujuan untuk versi 4 bahasa , sehingga perilaku ini akan berubah di masa depan.
Michael Homer
9
@MichaelHomer: Rupanya itu sudah diperbaiki, "πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘¦".countmengevaluasi 1dengan Xcode 9 beta saat ini dan Swift 4.
Martin R
5
Wow. Ini luar biasa. Tapi sekarang saya menjadi nostalgia untuk masa lalu ketika masalah terburuk yang saya miliki dengan string adalah apakah mereka menggunakan C atau encode gaya Pascal.
Owen Godfrey
2
Saya mengerti mengapa standar Unicode mungkin perlu untuk mendukung ini, tetapi manusia, ini adalah kekacauan overengineered, jika ada: /
Reinstate Monica
110

Masalah pertama adalah Anda menjembatani ke Yayasan dengan contains(Swift Stringbukan a Collection), jadi ini adalah NSStringperilaku, yang saya tidak percaya menangani Emoji yang dikomposisi sekuat Swift. Yang mengatakan, Swift saya percaya sedang mengimplementasikan Unicode 8 sekarang, yang juga perlu revisi di sekitar situasi ini di Unicode 10 (jadi ini semua dapat berubah ketika mereka menerapkan Unicode 10; Saya belum menggali apakah mau atau tidak).

Untuk mempermudah, mari singkirkan Foundation, dan gunakan Swift, yang memberikan pandangan yang lebih eksplisit. Kami akan mulai dengan karakter:

"πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘¦".characters.forEach { print($0) }
πŸ‘©β€
πŸ‘©β€
πŸ‘§β€
πŸ‘¦

BAIK. Itu yang kami harapkan. Tapi itu bohong. Mari kita lihat apa sebenarnya karakter-karakter itu.

"πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘¦".characters.forEach { print(String($0).unicodeScalars.map{$0}) }
["\u{0001F469}", "\u{200D}"]
["\u{0001F469}", "\u{200D}"]
["\u{0001F467}", "\u{200D}"]
["\u{0001F466}"]

Ah ... Jadi begitu ["πŸ‘©ZWJ", "πŸ‘©ZWJ", "πŸ‘§ZWJ", "πŸ‘¦"]. Itu membuat semuanya sedikit lebih jelas. πŸ‘© bukan anggota dari daftar ini (ini "πŸ‘©ZWJ"), tetapi πŸ‘¦ adalah anggota.

Masalahnya adalah itu Characteradalah "cluster grapheme," yang menyusun berbagai hal bersama-sama (seperti melampirkan ZWJ). Apa yang sebenarnya Anda cari adalah skalar unicode. Dan itu bekerja persis seperti yang Anda harapkan:

"πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘¦".unicodeScalars.contains("πŸ‘©") // true
"πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘¦".unicodeScalars.contains("\u{200D}") // true
"πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘¦".unicodeScalars.contains("πŸ‘§") // true
"πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘¦".unicodeScalars.contains("πŸ‘¦") // true

Dan tentu saja kita juga dapat mencari karakter aktual yang ada di sana:

"πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘¦".characters.contains("πŸ‘©\u{200D}") // true

(Ini duplikat poin Ben Leggiero. Saya memposting ini sebelum memperhatikan dia menjawab. Meninggalkan kalau-kalau itu lebih jelas bagi siapa pun.)

Rob Napier
sumber
Apa artinya ZWJberdiri?
LinusGeffarth
2
Zero Width Joiner
Rob Napier
@RobNapier di Swift 4, Stringdiduga diubah kembali menjadi tipe koleksi. Apakah itu mempengaruhi jawaban Anda?
Ben Leggiero
Tidak. Itu baru saja mengubah hal-hal seperti berlangganan. Itu tidak mengubah cara Karakter bekerja.
Rob Napier
75

Tampaknya Swift menganggap a ZWJmenjadi cluster grapheme yang diperluas dengan karakter yang mendahuluinya. Kita bisa melihat ini ketika memetakan array karakter ke unicodeScalars:

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

Ini mencetak yang berikut dari LLDB:

β–Ώ 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}"

Selain itu, .containsgrup memperluas cluster grapheme menjadi satu karakter. Misalnya, mengambil karakter hangul α„’, α…‘dan ᆫ(yang menggabungkan untuk membuat kata Korea untuk "satu": ᄒᅑᆫ):

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

Ini tidak dapat menemukan α„’karena tiga codepoint dikelompokkan ke dalam satu cluster yang bertindak sebagai satu karakter. Demikian pula, \u{1F469}\u{200D}( WOMAN ZWJ) adalah satu cluster, yang bertindak sebagai satu karakter.

Ben Leggiero
sumber
19

Jawaban lain membahas apa yang dilakukan Swift, tetapi jangan terlalu mendetail tentang mengapa.

Apakah Anda berharap "Γ…" sama dengan "Γ…"? Saya harap Anda akan melakukannya.

Salah satunya adalah surat dengan combiner, yang lain adalah karakter yang terdiri tunggal. Anda dapat menambahkan banyak penggabung berbeda ke karakter dasar, dan manusia masih akan menganggapnya sebagai karakter tunggal. Untuk menangani perbedaan ini, konsep grapheme dibuat untuk mewakili apa yang manusia anggap sebagai karakter terlepas dari codepoint yang digunakan.

Sekarang layanan pesan teks telah menggabungkan karakter ke emoji grafis selama bertahun-tahun :)Β β†’Β  πŸ™‚. Jadi berbagai emoji ditambahkan ke Unicode.
Layanan ini juga mulai menggabungkan emoji bersama menjadi emoji komposit.
Tentu saja tidak ada cara yang masuk akal untuk menyandikan semua kombinasi yang mungkin menjadi titik-titik kod tersendiri, jadi The Unicode Consortium memutuskan untuk memperluas konsep grapheme untuk mencakup karakter-karakter komposit ini.

Apa ini intinya adalah "πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘¦"harus dianggap sebagai "grapheme cluster" tunggal jika Anda mencoba untuk bekerja dengannya di tingkat grapheme, seperti yang dilakukan Swift secara default.

Jika Anda ingin memeriksa apakah mengandung "πŸ‘¦"sebagai bagian dari itu, maka Anda harus turun ke level yang lebih rendah.


Saya tidak tahu sintaks Swift jadi di sini ada beberapa Perl 6 yang memiliki tingkat dukungan yang sama untuk Unicode.
(Perl 6 mendukung Unicode versi 9 sehingga mungkin ada perbedaan)

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

Mari kita turun satu level

# 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

Naik ke level ini bisa membuat beberapa hal lebih sulit.

my @match = "πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘¦".ords;
my $l = @match.elems;
say @components.rotor( $l => 1-$l ).grep(@match).Bool; # True

Saya berasumsi bahwa .containsdalam Swift membuatnya lebih mudah, tetapi itu tidak berarti tidak ada hal lain yang menjadi lebih sulit.

Bekerja pada level ini membuatnya lebih mudah untuk secara tidak sengaja memecah string di tengah karakter komposit misalnya.


Apa yang secara tidak sengaja Anda tanyakan adalah mengapa representasi level yang lebih tinggi ini tidak berfungsi seperti representasi level yang lebih rendah. Jawabannya tentu saja, itu tidak seharusnya.

Jika Anda bertanya pada diri sendiri " mengapa ini harus begitu rumit ", jawabannya tentu saja " manusia ".

Brad Gilbert
sumber
4
Anda kehilangan saya di baris contoh terakhir Anda; apa yang harus rotordan greplakukan di sini? Dan apa itu 1-$l?
Ben Leggiero
4
Istilah "grapheme" setidaknya berusia 50 tahun. Unicode memperkenalkannya ke standar karena mereka sudah menggunakan istilah "karakter" untuk mengartikan sesuatu yang sangat berbeda dari apa yang biasanya dianggap sebagai karakter. Saya dapat membaca apa yang Anda tulis sebagai konsisten dengan itu tetapi mencurigai orang lain mungkin mendapatkan kesan yang salah, maka komentar ini (semoga klarifikasi).
raiph
2
@ BenLeggiero Pertama rotor,. Kode say (1,2,3,4,5,6).rotor(3)menghasilkan ((1 2 3) (4 5 6)). Itu daftar daftar, masing-masing panjangnya 3. say (1,2,3,4,5,6).rotor(3=>-2)menghasilkan yang sama kecuali sublist kedua dimulai dengan 2bukannya 4, yang ketiga dengan 3, dan seterusnya, menghasilkan ((1 2 3) (2 3 4) (3 4 5) (4 5 6)). Jika @matchmengandung "πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘¦".ordsmaka kode @ Brad hanya membuat satu sublist, jadi =>1-$lbitnya tidak relevan (tidak digunakan). Hanya relevan jika @matchlebih pendek dari @components.
raiph
1
grepmencoba untuk mencocokkan setiap elemen dalam undangannya (dalam hal ini, daftar sublists dari @components). Ia mencoba untuk mencocokkan setiap elemen terhadap argumen pencocokannya (dalam hal ini, @match). The .Boolkemudian kembali Truejika dan hanya jika grepmenghasilkan setidaknya satu pertandingan.
raiph
18

Pembaruan Swift 4.0

String menerima banyak revisi dalam pembaruan Swift 4, seperti yang didokumentasikan dalam SE-0163 . Dua emoji digunakan untuk demo ini yang mewakili dua struktur berbeda. Keduanya dikombinasikan dengan urutan emoji.

πŸ‘πŸ½adalah kombinasi dari dua emoji, πŸ‘dan🏽

πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘¦adalah kombinasi empat emoji, dengan penghubung lebar nol tersambung. Formatnya adalahπŸ‘©β€joinerπŸ‘©β€joinerπŸ‘§β€joinerπŸ‘¦

1. Hitungan

Dalam Swift 4.0 emoji dihitung sebagai cluster grapheme. Setiap emoji dihitung sebagai 1. countProperti ini juga tersedia secara langsung untuk string. Jadi Anda bisa langsung menyebutnya seperti ini.

"πŸ‘πŸ½".count  // 1. Not available on swift 3
"πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘¦".count  // 1. Not available on swift 3

Larik karakter string juga dihitung sebagai cluster grapheme di Swift 4.0, sehingga kedua kode berikut mencetak 1. Kedua emoji ini adalah contoh dari urutan emoji, di mana beberapa emoji digabungkan bersama-sama dengan atau tanpa penggabung lebar nol di \u{200d}antara mereka. Pada swift 3.0, array karakter string tersebut memisahkan setiap emoji dan menghasilkan array dengan banyak elemen (emoji). Joiner diabaikan dalam proses ini. Namun, dalam Swift 4.0, array karakter melihat semua emoji sebagai satu kesatuan. Sehingga emoji apa pun akan selalu menjadi 1.

"πŸ‘πŸ½".characters.count  // 1. In swift 3, this prints 2
"πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘¦".characters.count  // 1. In swift 3, this prints 4

unicodeScalars tetap tidak berubah dalam Swift 4. Ini memberikan karakter Unicode unik dalam string yang diberikan.

"πŸ‘πŸ½".unicodeScalars.count  // 2. Combination of two emoji
"πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘¦".unicodeScalars.count  // 7. Combination of four emoji with joiner between them

2. Berisi

Di Swift 4.0, containsmetode mengabaikan join lebar nol di emoji. Jadi mengembalikan true untuk salah satu dari empat komponen emoji "πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘¦", dan mengembalikan false jika Anda memeriksa penggabung. Namun, di Swift 3.0, joiner tidak diabaikan dan digabungkan dengan emoji di depannya. Jadi, ketika Anda memeriksa apakah "πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘¦"berisi tiga emoji komponen pertama, hasilnya akan salah

"πŸ‘πŸ½".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
Fangming
sumber
0

Emoji, seperti standar unicode, sangat rumit. Nada kulit, jenis kelamin, pekerjaan, kelompok orang, urutan pengganda lebar nol, bendera (2 karakter unicode) dan komplikasi lainnya dapat membuat parsing emoji berantakan. Pohon Natal, Sepotong Pizza, atau Tumpukan Poop dapat diwakili dengan satu titik kode Unicode. Belum lagi ketika emoji baru diperkenalkan, ada penundaan antara dukungan iOS dan rilis emoji. Itu dan fakta bahwa berbagai versi iOS mendukung versi standar unicode yang berbeda.

TL; DR. Saya telah bekerja pada fitur-fitur ini dan membuka sumber perpustakaan saya adalah penulis untuk JKEmoji untuk membantu string parse dengan emoji. Itu membuat penguraian semudah:

print("I love these emojis πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘¦πŸ’ͺ🏾πŸ§₯πŸ‘§πŸΏπŸŒˆ".emojiCount)

5

Itu dilakukan dengan memperbarui basis data lokal dari semua emoji yang dikenali sebagai versi unicode terbaru ( 12.0 baru-baru ini) dan merujuk silang mereka dengan apa yang dikenali sebagai emoji yang valid dalam versi OS yang sedang berjalan dengan melihat representasi bitmap dari karakter emoji yang tidak dikenal.

CATATAN

Jawaban sebelumnya dihapus untuk mengiklankan perpustakaan saya tanpa menyatakan dengan jelas bahwa saya adalah pembuatnya. Saya mengakui ini lagi.

Joe
sumber
2
Sementara saya terkesan dengan perpustakaan Anda, dan saya melihat bagaimana itu umumnya terkait dengan topik yang ada, saya tidak melihat bagaimana ini berhubungan langsung dengan pertanyaan
Ben Leggiero