Das Zeichen 👩👩👧👦 (Familie mit zwei Frauen, einem Mädchen und einem Jungen) ist als solches codiert:
U+1F469
WOMAN
,
U+200D
ZWJ
,
U+1F469
WOMAN
,
U+200D
ZWJ
,
U+1F467
GIRL
,
U+200D
ZWJ
,
U+1F466
BOY
Es ist also sehr interessant codiert; das perfekte Ziel für einen Unit-Test. Swift scheint jedoch nicht zu wissen, wie er damit umgehen soll. Folgendes meine ich:
"👩👩👧👦".contains("👩👩👧👦") // true
"👩👩👧👦".contains("👩") // false
"👩👩👧👦".contains("\u{200D}") // false
"👩👩👧👦".contains("👧") // false
"👩👩👧👦".contains("👦") // true
Also sagt Swift, dass es sich selbst (gut) und einen Jungen (gut!) Enthält. Aber es heißt dann, dass es keine Frau, kein Mädchen oder keinen Tischler mit einer Breite von Null enthält. Was passiert hier? Warum weiß Swift, dass es einen Jungen enthält, aber keine Frau oder kein Mädchen? Ich könnte verstehen, wenn es es als ein einzelnes Zeichen behandelt und nur erkennt, dass es sich selbst enthält, aber die Tatsache, dass es eine Unterkomponente hat und keine anderen, verwirrt mich.
Das ändert sich nicht, wenn ich so etwas benutze "👩".characters.first!
.
Noch verwirrender ist Folgendes:
let manual = "\u{1F469}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}"
Array(manual.characters) // ["👩", "👩", "👧", "👦"]
Obwohl ich die ZWJs dort platziert habe, spiegeln sie sich nicht im Zeichenarray wider. Was folgte, war ein wenig aussagekräftig:
manual.contains("👩") // false
manual.contains("👧") // false
manual.contains("👦") // true
Ich habe also das gleiche Verhalten mit dem Zeichenarray ... was äußerst ärgerlich ist, da ich weiß, wie das Array aussieht.
Das ändert sich auch nicht, wenn ich so etwas benutze "👩".characters.first!
.
"👩👩👧👦".contains("\u{200D}")
immer noch false zurück, nicht sicher, ob dies ein Fehler oder eine Funktion ist.Antworten:
Dies hängt damit zusammen, wie der
String
Typ in Swift funktioniert und wie diecontains(_:)
Methode funktioniert.Das '👩👩👧👦' ist eine sogenannte Emoji-Sequenz, die als ein sichtbares Zeichen in einer Zeichenfolge gerendert wird. Die Sequenz besteht aus
Character
Objekten und gleichzeitig ausUnicodeScalar
Objekten.Wenn Sie die Zeichenanzahl der Zeichenfolge überprüfen, sehen Sie, dass sie aus vier Zeichen besteht. Wenn Sie die Anzahl der Unicode-Skalare überprüfen, wird ein anderes Ergebnis angezeigt:
Wenn Sie nun die Zeichen analysieren und ausdrucken, sehen Sie, was wie normale Zeichen aussieht. Tatsächlich enthalten die drei ersten Zeichen jedoch sowohl einen Emoji- als auch einen Joiner mit einer Breite von Null in ihren
UnicodeScalarView
:Wie Sie sehen können, enthält nur das letzte Zeichen keinen Joiner mit der Breite Null. Wenn Sie die
contains(_:)
Methode verwenden, funktioniert sie also wie erwartet. Da Sie nicht mit Emoji vergleichen, die Joiner mit einer Breite von Null enthalten, findet die Methode nur für das letzte Zeichen eine Übereinstimmung.Wenn Sie
String
ein Emoji-Zeichen erstellen, das mit einem Joiner mit der Breite Null endet, und es an diecontains(_:)
Methode übergeben, wird es ebenfalls ausgewertetfalse
. Dies hat damit zu tun,contains(_:)
dass es genau dasselbe ist wierange(of:) != nil
, was versucht, eine genaue Übereinstimmung mit dem gegebenen Argument zu finden. Da Zeichen, die mit einem Joiner mit der Breite Null enden, eine unvollständige Sequenz bilden, versucht die Methode, eine Übereinstimmung für das Argument zu finden, während Zeichen, die mit Joinern mit der Breite Null enden, zu einer vollständigen Sequenz kombiniert werden. Dies bedeutet, dass die Methode niemals eine Übereinstimmung findet, wenn:Demonstrieren:
Da der Vergleich jedoch nur nach vorne schaut, können Sie mehrere andere vollständige Sequenzen innerhalb der Zeichenfolge finden, indem Sie rückwärts arbeiten:
Die einfachste Lösung wäre, eine bestimmte Vergleichsoption für die
range(of:options:range:locale:)
Methode bereitzustellen . Die OptionString.CompareOptions.literal
führt den Vergleich mit einer genauen Zeichen-für-Zeichen-Äquivalenz durch . Nebenbei bemerkt, was hier mit Zeichen gemeint ist, ist nicht der SwiftCharacter
, sondern die UTF-16-Darstellung sowohl der Instanz als auch der Vergleichszeichenfolge. DaString
jedoch keine fehlerhafte UTF-16 zulässig ist, entspricht dies im Wesentlichen dem Vergleich des Unicode-Skalars Darstellung.Hier habe ich die
Foundation
Methode überladen. Wenn Sie also die ursprüngliche Methode benötigen, benennen Sie diese oder etwas anderes um:Jetzt funktioniert die Methode mit jedem Zeichen so, wie es "sollte", auch bei unvollständigen Sequenzen:
quelle
"👩👩👧👦".count
bewertet1
mit der aktuellen Xcode 9 Beta und Swift 4.Das erste Problem ist, dass Sie eine Brücke zur Foundation schlagen
contains
(SwiftsString
ist kein aCollection
). Dies ist also einNSString
Verhalten, von dem ich glaube, dass es Emoji nicht so kraftvoll handhabt wie Swift. Ich glaube jedoch, dass Swift derzeit Unicode 8 implementiert, was auch eine Überarbeitung dieser Situation in Unicode 10 erforderlich machte (dies kann sich also ändern, wenn Unicode 10 implementiert wird; ich habe nicht untersucht, ob dies der Fall ist oder nicht).Lassen Sie uns zur Vereinfachung Foundation loswerden und Swift verwenden, das explizitere Ansichten bietet. Wir beginnen mit Charakteren:
OK. Das haben wir erwartet. Aber es ist eine Lüge. Mal sehen, was diese Charaktere wirklich sind.
Ah ... also ist es
["👩ZWJ", "👩ZWJ", "👧ZWJ", "👦"]
. Das macht alles etwas klarer. 👩 ist kein Mitglied dieser Liste (es ist "👩ZWJ"), aber 👦 ist Mitglied.Das Problem ist, dass
Character
es sich um einen "Graphemcluster" handelt, der Dinge zusammensetzt (wie das Anhängen des ZWJ). Was Sie wirklich suchen, ist ein Unicode-Skalar. Und das funktioniert genau so, wie Sie es erwarten:Und natürlich können wir auch nach dem tatsächlichen Charakter suchen, der sich darin befindet:
(Dies dupliziert stark Ben Leggieros Punkte. Ich habe dies gepostet, bevor ich bemerkte, dass er geantwortet hatte.
quelle
ZWJ
?String
wurde angeblich wieder in einen Sammlungstyp geändert. Beeinflusst das Ihre Antwort überhaupt?Es scheint, dass Swift a
ZWJ
als erweiterten Graphemcluster mit dem Zeichen unmittelbar davor betrachtet. Wir können dies sehen, wenn wir das Array von Zeichen auf Folgendes zuordnenunicodeScalars
:Dies druckt Folgendes aus der LLDB:
Außerdem
.contains
gruppieren Gruppen erweiterte Graphemcluster zu einem einzigen Zeichen. Zum Beispiel nimmt die Hangul - Zeichenᄒ
,ᅡ
undᆫ
(der das koreanische Wort für „eins“ machen kombinieren:한
):Dies konnte nicht gefunden werden,
ᄒ
da die drei Codepunkte in einem Cluster zusammengefasst sind, der als ein Zeichen fungiert. In ähnlicher Weise ist\u{1F469}\u{200D}
(WOMAN
ZWJ
) ein Cluster, der als ein Zeichen fungiert.quelle
In den anderen Antworten wird erläutert, was Swift tut, aber es wird nicht näher darauf eingegangen, warum.
Erwarten Sie, dass "Å" gleich "Å" ist? Ich gehe davon aus, dass du es tun würdest.
Einer davon ist ein Buchstabe mit einem Kombinierer, der andere ist ein einzelnes zusammengesetztes Zeichen. Sie können einem Basischarakter viele verschiedene Kombinierer hinzufügen, und ein Mensch würde ihn immer noch als einen einzelnen Charakter betrachten. Um mit dieser Art von Diskrepanz umzugehen, wurde das Konzept eines Graphems erstellt, um darzustellen, was ein Mensch unabhängig von den verwendeten Codepunkten als Zeichen betrachten würde.
Seit Jahren kombinieren SMS-Dienste Zeichen seit Jahren zu grafischen Emoji
:)
→🙂
. So wurden Unicode verschiedene Emoji hinzugefügt.Diese Dienste haben auch begonnen, Emoji zu zusammengesetzten Emoji zu kombinieren.
Es gibt natürlich keine vernünftige Möglichkeit, alle möglichen Kombinationen in einzelne Codepunkte zu kodieren. Daher hat das Unicode-Konsortium beschlossen, das Konzept der Grapheme zu erweitern, um diese zusammengesetzten Zeichen zu erfassen.
Worauf es ankommt,
"👩👩👧👦"
sollte als einzelner "Graphemcluster" betrachtet werden, wenn Sie versuchen, auf Graphemebene damit zu arbeiten, wie dies Swift standardmäßig tut.Wenn Sie überprüfen möchten, ob es
"👦"
einen Teil davon enthält , sollten Sie auf eine niedrigere Ebene gehen.Ich kenne die Swift-Syntax nicht, daher hier einige Perl 6, die Unicode ähnlich unterstützen.
(Perl 6 unterstützt Unicode Version 9, daher kann es zu Unstimmigkeiten kommen.)
Lass uns ein Level runter gehen
Ein Abstieg auf dieses Niveau kann jedoch einige Dinge schwieriger machen.
Ich gehe davon aus, dass
.contains
Swift dies einfacher macht, aber das bedeutet nicht, dass es keine anderen Dinge gibt, die schwieriger werden.Wenn Sie auf dieser Ebene arbeiten, ist es viel einfacher, beispielsweise versehentlich eine Zeichenfolge in der Mitte eines zusammengesetzten Zeichens zu teilen.
Was Sie versehentlich fragen, ist, warum diese Darstellung auf höherer Ebene nicht so funktioniert wie eine Darstellung auf niedrigerer Ebene. Die Antwort ist natürlich, es soll nicht.
Wenn Sie sich fragen, warum das so kompliziert sein muss , lautet die Antwort natürlich „ Menschen “.
quelle
rotor
undgrep
machen hier? Und was ist1-$l
?rotor
. Der Codesay (1,2,3,4,5,6).rotor(3)
ergibt((1 2 3) (4 5 6))
. Das ist eine Liste von Listen, jede Länge3
.say (1,2,3,4,5,6).rotor(3=>-2)
ergibt dasselbe, außer dass die zweite Unterliste2
eher mit als4
die dritte mit usw. beginnt3
und ergibt((1 2 3) (2 3 4) (3 4 5) (4 5 6))
. Wenn@match
enthält"👩👩👧👦".ords
dann @ Brads Code erstellt nur einen sublist, so dass das=>1-$l
Bit ist nicht relevant (nicht verwendet). Es ist nur relevant, wenn@match
es kürzer als ist@components
.grep
versucht, jedes Element in seinem Invokanten abzugleichen (in diesem Fall eine Liste von Unterlisten von@components
). Es wird versucht, jedes Element mit seinem Matcher-Argument abzugleichen (in diesem Fall@match
). Das gibt.Bool
dann zurück,True
wenn dasgrep
mindestens eine Übereinstimmung erzeugt.Schnelles 4.0-Update
String hat im Swift 4-Update viele Revisionen erhalten, wie in SE-0163 dokumentiert . Für diese Demo werden zwei Emoji verwendet, die zwei verschiedene Strukturen darstellen. Beide werden mit einer Folge von Emoji kombiniert.
👍🏽
ist die Kombination von zwei Emoji👍
und🏽
👩👩👧👦
ist die Kombination von vier Emoji mit angeschlossenem Joiner mit einer Breite von Null. Das Format ist👩joiner👩joiner👧joiner👦
1. Zählt
In Swift 4.0 wird Emoji als Graphemcluster gezählt. Jedes einzelne Emoji wird als 1 gezählt. Die
count
Eigenschaft ist auch direkt für Zeichenfolgen verfügbar. Sie können es also direkt so nennen.Das Zeichenarray einer Zeichenfolge wird in Swift 4.0 auch als Graphemcluster gezählt, sodass beide folgenden Codes 1 ausgeben. Diese beiden Emoji sind Beispiele für Emoji-Sequenzen, bei denen mehrere Emoji mit oder ohne Joiner mit der Breite Null kombiniert
\u{200d}
werden. In Swift 3.0 trennt das Zeichenarray einer solchen Zeichenfolge jedes Emoji und führt zu einem Array mit mehreren Elementen (Emoji). Der Joiner wird dabei ignoriert. In Swift 4.0 sieht das Zeichenarray jedoch alle Emoji als ein Stück. Das von jedem Emoji wird also immer 1 sein.unicodeScalars
bleibt in Swift 4 unverändert. Es enthält die eindeutigen Unicode-Zeichen in der angegebenen Zeichenfolge.2. Enthält
In Swift 4.0
contains
ignoriert die Methode den Joiner mit der Breite Null in Emoji. Es gibt also true für eine der vier Emoji-Komponenten von zurück"👩👩👧👦"
und false, wenn Sie nach dem Joiner suchen. In Swift 3.0 wird der Joiner jedoch nicht ignoriert und mit dem davor stehenden Emoji kombiniert. Wenn Sie also überprüfen, ob"👩👩👧👦"
die ersten drei Komponenten Emoji enthalten, ist das Ergebnis falschquelle
Emojis sind ähnlich wie der Unicode-Standard täuschend kompliziert. Hauttöne, Geschlechter, Jobs, Personengruppen, Joiner-Sequenzen mit einer Breite von Null, Flaggen (Unicode mit 2 Zeichen) und andere Komplikationen können das Parsen von Emoji unübersichtlich machen. Ein Weihnachtsbaum, ein Stück Pizza oder ein Haufen Kacke können alle mit einem einzigen Unicode-Codepunkt dargestellt werden. Ganz zu schweigen davon, dass es bei der Einführung neuer Emojis zu einer Verzögerung zwischen der iOS-Unterstützung und der Emoji-Veröffentlichung kommt. Das und die Tatsache, dass verschiedene Versionen von iOS verschiedene Versionen des Unicode-Standards unterstützen.
TL; DR. Ich habe an diesen Funktionen gearbeitet und eine Bibliothek eröffnet. Ich bin der Autor von JKEmoji , um das Parsen von Strings mit Emojis zu unterstützen. Es macht das Parsen so einfach wie:
Dazu wird routinemäßig eine lokale Datenbank aller erkannten Emojis ab der neuesten Unicode-Version (ab 12.0 ) aktualisiert und anhand der Bitmap-Darstellung von mit den in der laufenden Betriebssystemversion als gültig erkannten Emojis verglichen ein nicht erkannter Emoji-Charakter.
HINWEIS
Eine vorherige Antwort wurde gelöscht, um für meine Bibliothek zu werben, ohne eindeutig anzugeben, dass ich der Autor bin. Ich erkenne das wieder an.
quelle