Postać 👩👩👧👦 (rodzina z dwiema kobietami, jedną dziewczynką i jednym chłopcem) jest zakodowana jako taka:
U+1F469
WOMAN
,
U+200D
ZWJ
,
U+1F469
WOMAN
,
U+200D
ZWJ
,
U+1F467
GIRL
,
U+200D
ZWJ
,
U+1F466
BOY
Jest więc bardzo ciekawie zakodowany; idealny cel do testu jednostkowego. Jednak wydaje się, że Swift nie wie, jak to leczyć. Oto co mam na myśli:
"👩👩👧👦".contains("👩👩👧👦") // true
"👩👩👧👦".contains("👩") // false
"👩👩👧👦".contains("\u{200D}") // false
"👩👩👧👦".contains("👧") // false
"👩👩👧👦".contains("👦") // true
Więc Swift mówi, że zawiera siebie (dobrze) i chłopca (dobrze!). Ale potem mówi, że nie zawiera kobiety, dziewczyny ani stolarki o zerowej szerokości. Co tu się dzieje? Dlaczego Swift wie, że zawiera chłopca, ale nie kobietę ani dziewczynkę? Zrozumiałbym, gdyby traktował go jako pojedynczą postać i rozpoznał tylko, że zawiera się w sobie, ale fakt, że dostał jeden podskładnik, a żadnych innych mnie nie zaskakuje.
Nie zmienia się to, jeśli używam czegoś takiego "👩".characters.first!
.
Jeszcze bardziej kłopotliwe jest to:
let manual = "\u{1F469}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}"
Array(manual.characters) // ["👩", "👩", "👧", "👦"]
Mimo że umieściłem tam ZWJ, nie są one odzwierciedlone w tablicy znaków. To, co nastąpiło później, było trochę mówiące:
manual.contains("👩") // false
manual.contains("👧") // false
manual.contains("👦") // true
Mam takie samo zachowanie z tablicą znaków ... co jest wyjątkowo denerwujące, ponieważ wiem, jak wygląda tablica.
To też się nie zmienia, jeśli użyję czegoś takiego "👩".characters.first!
.
"👩👩👧👦".contains("\u{200D}")
nadal zwraca false, nie jestem pewien, czy to błąd, czy funkcja.Odpowiedzi:
Ma to związek z tym, jak
String
typ działa w Swift i jakcontains(_:)
działa metoda.„👩👩👧👦” to tak zwana sekwencja emoji, która jest renderowana jako jeden widoczny znak w ciągu. Sekwencja składa się z
Character
obiektów, a jednocześnie składa się zUnicodeScalar
obiektów.Jeśli sprawdzisz liczbę znaków ciągu, zobaczysz, że składa się on z czterech znaków, a jeśli sprawdzisz liczbę skalarną Unicode, wyświetli się inny wynik:
Teraz, gdy przeanalizujesz znaki i wydrukujesz je, zobaczysz coś, co wydaje się normalne, ale w rzeczywistości trzy pierwsze znaki zawierają zarówno emoji, jak i łącznik o zerowej szerokości
UnicodeScalarView
:Jak widać, tylko ostatni znak nie zawiera łącznika o zerowej szerokości, więc przy użyciu tej
contains(_:)
metody działa tak, jak można się spodziewać. Ponieważ nie porównujesz z emoji zawierającymi łączniki o zerowej szerokości, metoda nie znajdzie dopasowania dla żadnego oprócz ostatniego znaku.Aby rozwinąć tę kwestię, jeśli utworzysz
String
kompozycję składającą się ze znaku emoji kończącego się łącznikiem o zerowej szerokości i przekażesz ją docontains(_:)
metody, to również oceni tofalse
. Ma to związek zcontains(_:)
byciem dokładnie takim samym jakrange(of:) != nil
, który próbuje znaleźć dokładne dopasowanie do podanego argumentu. Ponieważ znaki kończące się łącznikiem o zerowej szerokości tworzą niekompletną sekwencję, metoda próbuje znaleźć dopasowanie dla argumentu, łącząc znaki kończące się łącznikami o zerowej szerokości w pełną sekwencję. Oznacza to, że metoda nigdy nie znajdzie dopasowania, jeśli:Aby zademonstrować:
Ponieważ jednak porównanie tylko patrzy w przyszłość, można znaleźć kilka innych pełnych sekwencji w ciągu, pracując wstecz:
Najłatwiejszym rozwiązaniem byłoby zapewnienie konkretnej opcji porównania z
range(of:options:range:locale:)
metodą. OpcjaString.CompareOptions.literal
wykonuje porównanie na podstawie dokładnej równoważności znak po znaku . Na marginesie, co oznaczało po znaku Oto nie SwiftCharacter
, ale UTF-16 reprezentacji obu instancji i porównania String - jednak, ponieważString
nie pozwala zniekształcone UTF-16, to jest w zasadzie równoważne porównując skalarne Unicode reprezentacja.Tutaj przeciążyłem
Foundation
metodę, więc jeśli potrzebujesz oryginalnej, zmień nazwę tej lub innej:Teraz metoda działa tak, jak „powinna” z każdym znakiem, nawet z niekompletnymi sekwencjami:
źródło
"👩👩👧👦".count
ocenia1
na obecną wersję Xcode 9 beta i Swift 4.Pierwszym problemem jest to, że łączysz się z Foundation
contains
(Swift'sString
nie jest aCollection
), więc jest toNSString
zachowanie, które nie wydaje mi się, że obsługuje skomponowane Emoji tak silnie jak Swift. To powiedziawszy, wydaje mi się, że Swift wdraża teraz Unicode 8, co również wymagało korekty wokół tej sytuacji w Unicode 10 (więc to wszystko może się zmienić, gdy implementują Unicode 10; nie zastanawiałem się, czy to zrobi, czy nie).Aby uprościć sprawę, pozbądźmy się Fundacji i użyj Swift, który zapewnia bardziej wyraźne widoki. Zaczniemy od postaci:
OK. Tego się spodziewaliśmy. Ale to kłamstwo. Zobaczmy, jakie naprawdę są te postacie.
Ach… Więc to jest
["👩ZWJ", "👩ZWJ", "👧ZWJ", "👦"]
. To sprawia, że wszystko jest trochę bardziej jasne. 👩 nie jest członkiem tej listy (jest to „👩ZWJ”), ale 👦 jest członkiem.Problem polega na tym, że
Character
jest to „klaster grafemiczny”, który tworzy rzeczy razem (np. Dołączanie ZWJ). To, czego tak naprawdę szukasz, to skalar unicode. I to działa dokładnie tak, jak się spodziewasz:I oczywiście możemy również poszukać rzeczywistej postaci, która się tam znajduje:
(To bardzo powiela punkty Bena Leggiero. Wysłałem to, zanim zauważyłem, że odpowiedział. Pozostawiając na wypadek, gdyby ktokolwiek był bardziej zrozumiały.)
źródło
ZWJ
?String
rzekomo został zmieniony z powrotem na typ kolekcji. Czy to w ogóle wpływa na twoją odpowiedź?Wygląda na to, że Swift uważa
ZWJ
rozszerzoną grupę grafemów z postacią bezpośrednio poprzedzającą. Widzimy to podczas mapowania tablicy znaków na ichunicodeScalars
:Spowoduje to wydrukowanie następującego pliku z LLDB:
Ponadto
.contains
grupy rozszerzyły klastry grafemów w jedną postać. Na przykład, biorąc HANGUL znakówᄒ
,ᅡ
orazᆫ
(które składają się na koreańskiej słowo „jeden”:한
):Nie można tego znaleźć,
ᄒ
ponieważ trzy punkty kodowe są zgrupowane w jeden klaster, który działa jak jeden znak. Podobnie\u{1F469}\u{200D}
(WOMAN
ZWJ
) to jeden klaster, który działa jak jeden znak.źródło
Pozostałe odpowiedzi omawiają to, co robi Swift, ale nie zawierają szczegółowych informacji na temat tego, dlaczego.
Czy spodziewasz się, że „Å” będzie równe „Å”? Oczekuję, że tak.
Jedna z nich to litera z łącznikiem, druga to pojedynczy złożony znak. Możesz dodać wiele różnych kombinacji do postaci podstawowej, a człowiek nadal uważa ją za pojedynczą postać. Aby poradzić sobie z tego rodzaju rozbieżnościami, stworzono koncepcję grafemu, która reprezentuje to, co człowiek uważa za postać, niezależnie od użytych współrzędnych kodowych.
Teraz usługi SMS-ów od lat łączą znaki w graficzne emoji
:)
→🙂
. Do Unicode dodano więc różne emoji.Usługi te zaczęły także łączyć emoji razem w emoji kompozytowe.
Oczywiście nie ma rozsądnego sposobu na zakodowanie wszystkich możliwych kombinacji w poszczególnych punktach kodowych, więc Konsorcjum Unicode postanowiło rozwinąć koncepcję grafemów, aby objąć te złożone znaki.
Sprowadza się to
"👩👩👧👦"
do pojedynczego „klastra grafemowego”, jeśli próbujesz z nim pracować na poziomie grafemowym, jak domyślnie robi Swift.Jeśli chcesz sprawdzić, czy zawiera
"👦"
to część, powinieneś zejść na niższy poziom.Nie znam składni Swift, więc oto Perl 6, który ma podobny poziom obsługi Unicode.
(Perl 6 obsługuje Unicode w wersji 9, więc mogą wystąpić rozbieżności)
Zejdźmy o poziom
Zejście na ten poziom może jednak utrudnić niektóre rzeczy.
Zakładam, że
.contains
w Swift to ułatwia, ale to nie znaczy, że nie ma innych rzeczy, które stałyby się trudniejsze.Praca na tym poziomie znacznie ułatwia na przykład przypadkowe podzielenie łańcucha w środku złożonego znaku.
Nieumyślnie pytasz, dlaczego ta reprezentacja wyższego poziomu nie działa tak jak reprezentacja niższego poziomu. Odpowiedź brzmi oczywiście, że nie powinna.
Jeśli zadajesz sobie pytanie „ dlaczego to musi być takie skomplikowane ”, odpowiedź brzmi oczywiście „ ludzie ”.
źródło
rotor
igrep
zrobić tutaj? A co to jest1-$l
?rotor
. Kodsay (1,2,3,4,5,6).rotor(3)
daje((1 2 3) (4 5 6))
. To lista list każdej długości3
.say (1,2,3,4,5,6).rotor(3=>-2)
daje to samo, z wyjątkiem tego, że druga podlista zaczyna się2
od4
, a trzecia z3
, i tak dalej, daje((1 2 3) (2 3 4) (3 4 5) (4 5 6))
. Jeśli@match
zawiera,"👩👩👧👦".ords
to kod @ Brada tworzy tylko jedną podlistę, więc=>1-$l
bit jest nieistotny (nieużywany). Jest to istotne tylko wtedy, gdy@match
jest krótsze niż@components
.grep
próbuje dopasować każdy element w jego wywoływaczu (w tym przypadku listę podlist@components
). Próbuje dopasować każdy element do argumentu matcher (w tym przypadku,@match
). Na.Bool
czym wracaTrue
IFFgrep
produkuje przynajmniej jeden mecz.Aktualizacja Swift 4.0
Ciąg otrzymał wiele poprawek w aktualizacji Swift 4, jak udokumentowano w SE-0163 . W tym pokazie używane są dwa emoji reprezentujące dwie różne struktury. Oba są połączone z sekwencją emoji.
👍🏽
to połączenie dwóch emoji👍
i🏽
👩👩👧👦
to kombinacja czterech emoji z podłączonym łącznikiem o zerowej szerokości. Format to👩joiner👩joiner👧joiner👦
1. Liczy się
W Swift 4.0 emoji jest liczone jako klaster grafemów. Każde emoji jest liczone jako 1.
count
Właściwość jest również bezpośrednio dostępna dla ciągu. Możesz więc bezpośrednio tak to nazwać.Tablica znaków ciągu jest również liczona jako klastry grafem w Swift 4.0, więc oba poniższe kody drukują 1. Te dwa emoji są przykładami sekwencji emoji, w których kilka emoji jest łączonych razem z łącznikiem o zerowej szerokości lub
\u{200d}
między nimi. W swift 3.0 tablica znaków takiego łańcucha oddziela każde emoji i daje tablicę z wieloma elementami (emoji). Łącznik jest w tym procesie ignorowany. Jednak w Swift 4.0 tablica znaków traktuje wszystkie emoji jako jeden element. Tak więc, dla każdego emoji zawsze będzie 1.unicodeScalars
pozostaje niezmieniony w Swift 4. Zapewnia unikalne znaki Unicode w podanym ciągu.2. Zawiera
W Swift 4.0
contains
metoda ignoruje łącznik o zerowej szerokości w emoji. Zwraca więc wartość true dla dowolnego z czterech składników emoji"👩👩👧👦"
i zwraca wartość false, jeśli zaznaczysz stolarkę. Jednak w Swift 3.0 łącznik nie jest ignorowany i jest łączony z emoji przed nim. Więc gdy sprawdzisz, czy"👩👩👧👦"
zawiera pierwsze trzy komponenty emoji, wynik będzie fałszywyźródło
Emoji, podobnie jak standard Unicode, są oszukańczo skomplikowane. Odcienie skóry, płeć, praca, grupy ludzi, sekwencje łączenia zerowej szerokości, flagi (Unicode 2 znaków) i inne komplikacje mogą powodować, że parsowanie emoji jest nieporządne. Choinkę, kawałek pizzy lub kupę kupy można przedstawić za pomocą pojedynczego punktu kodu Unicode. Nie wspominając o tym, że po wprowadzeniu nowych emoji istnieje opóźnienie między obsługą iOS a wydaniem emoji. To i fakt, że różne wersje iOS obsługują różne wersje standardu Unicode.
TL; DR. Pracowałem nad tymi funkcjami i otworzyłem bibliotekę. Jestem autorem JKEmoji, który pomaga parsować ciągi znaków za pomocą emoji. Dzięki temu parsowanie jest tak proste jak:
Odbywa się to poprzez rutynowe odświeżanie lokalnej bazy danych wszystkich rozpoznawanych emoji od najnowszej wersji Unicode (od 12.0 od niedawna) i odsyłanie ich do tego, co jest rozpoznawane jako prawidłowe emoji w uruchomionej wersji systemu operacyjnego, poprzez przeglądanie mapy bitowej nierozpoznany znak emoji.
UWAGA
Poprzednia odpowiedź została usunięta za reklamowanie mojej biblioteki bez wyraźnego stwierdzenia, że jestem autorem. Ponownie to potwierdzam.
źródło