Dlaczego znaki emoji są takie jak

Znak

Author: Robert Harvey, 2017-04-25

5 answers

Ma to związek z tym, jak typ String działa w języku Swift i jak działa metoda contains(_:).

' jest sekwencją emoji, która jest renderowana jako jeden widoczny znak w łańcuchu znaków. Sekwencja składa się z Character obiektów, a jednocześnie składa się z UnicodeScalar obiektów.

Jeśli sprawdzisz liczbę znaków łańcucha, zobaczysz, że składa się on z czterech znaków, podczas gdy jeśli sprawdzisz liczbę skalarów unicode, pokaże ci inny wynik:

print("‍‍‍".characters.count)     // 4
print("‍‍‍".unicodeScalars.count) // 7
Jeśli przeanalizujesz znaki i wydrukujesz je, zobaczysz, co wydaje się normalnymi znakami, ale w rzeczywistości trzy pierwsze znaki zawierają zarówno emoji, jak i joiner o zerowej szerokości w swoich 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"]

Jak widać, tylko ostatni znak nie zawiera joinera o zerowej szerokości, więc przy użyciu metody contains(_:) działa tak, jak można się spodziewać. Ponieważ nie porównujesz Emotikon zawierających łączniki o zerowej szerokości, metoda nie znajdzie dopasowania dla każdego, oprócz ostatniej postaci.

Aby to rozwinąć, jeśli utworzysz String, który składa się ze znaku emoji zakończonego joinerem o zerowej szerokości i przekażesz go do metody contains(_:), będzie on również oceniany do false. Ma to związek z tym, że contains(_:) jest dokładnie tym samym co range(of:) != nil, które próbuje znaleźć dokładne dopasowanie do podanego argumentu. Ponieważ znaki kończące się znakiem o zerowej szerokości tworzą niekompletną sekwencję, metoda próbuje znaleźć dopasowanie do argumentu podczas łączenia znaków kończące się łącznikiem o zerowej szerokości w kompletną sekwencję. Oznacza to, że metoda nigdy nie znajdzie dopasowania, jeśli: {]}

  1. argument kończy się łącznikiem o zerowej szerokości i
  2. ciąg znaków do analizy nie zawiera niekompletnej sekwencji (tzn. kończy się znakiem o zerowej szerokości i nie następuje po nim zgodny znak).

Aby zademonstrować:

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

Jednak, ponieważ porównanie tylko patrzy w przyszłość, można znaleźć kilka innych kompletnych sekwencji w obrębie ciąg przez pracę do tyłu:

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

Najprostszym rozwiązaniem byłoby dostarczenie konkretnej opcji porównania do range(of:options:range:locale:) metoda. Opcja String.CompareOptions.literal wykonuje porównanie na dokładnej równoważności znak po znaku. Na marginesie, to, co oznacza znak, to Nie Swift Character, ale reprezentacja UTF-16 zarówno instancji, jak i ciągu porównawczego – jednak, ponieważ String nie pozwala na zniekształcone UTF-16, jest to zasadniczo równoważne porównywanie reprezentacji skalarnej Unicode.

Tutaj przeładowałem metodę Foundation, więc jeśli potrzebujesz oryginalnej, Zmień nazwę tej lub coś w tym stylu:

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

Teraz metoda działa tak, jak powinna z każdym znakiem, nawet z niekompletnymi sekwencjami:

s.contains("")          // true
s.contains("\u{200d}")  // true
s.contains("\u{200d}")    // true
 367
Author: xoudini,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-28 18:58:09

Pierwszy problem polega na tym, że łączysz się z Fundacją za pomocą contains (Swift ' s String nie jest Collection), więc jest to NSString zachowanie, które nie wydaje mi się tak silne, jak Swift. To powiedziawszy, Swift wierzę, że implementuje Unicode 8 w tej chwili, który również potrzebował rewizji wokół tej sytuacji w Unicode 10 (więc to wszystko może się zmienić, gdy implementują Unicode 10; nie grzebałem w tym, czy będzie, czy nie).

Aby uprościć sprawę, pozbądźmy się fundamentów i użyjmy Swift, który zapewnia bardziej wyraźne widoki. Zaczniemy od znaków:

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

OK. Tego się spodziewaliśmy. Ale to kłamstwo. Zobaczmy, czym naprawdę są te postacie.

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

Ah... więc to jest ["ZWJ", "ZWJ", "ZWJ", ""]. To sprawia, że wszystko jest bardziej jasne. nie jest członkiem tej listy (to "ZWJ"), ale jest członkiem.

Problem polega na tym, że Character jest "klastrem grafemów", który komponuje rzeczy razem (jak dołączanie ZWJ). To, czego naprawdę szukasz, to unicode Skalar. A to działa dokładnie tak, jak się spodziewasz:

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

I oczywiście możemy również szukać rzeczywistej postaci, która tam jest:

"‍‍‍".characters.contains("\u{200D}") // true
Ben Leggiero jest jednym z najlepszych graczy w historii. Zamieściłam to, zanim zauważyłam, że odpowiedział. Odejście w razie gdyby było to dla kogokolwiek jasne.)
 100
Author: Rob Napier,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-25 19:24:24

Wydaje się, że Swift uważa ZWJ za Rozszerzony klaster grafów ze znakiem bezpośrednio poprzedzającym go. Możemy to zobaczyć podczas mapowania tablicy znaków do ich unicodeScalars:

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

To drukuje następujące z 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}"

Dodatkowo, .contains grupuje rozszerzone klastry grafemów w jeden znak. Na przykład, biorąc znaki hangul , , i (które łączą się, aby Koreańskie słowo oznaczało " jeden": 한):

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

To nie może znaleźć ponieważ trzy punkty kodowe są zgrupowane w jeden klaster, który działa jako jeden znak. Podobnie, \u{1F469}\u{200D} (WOMAN ZWJ) jest jednym klastrem, który działa jako jedna postać.

 70
Author: Ben Leggiero,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-25 19:10:54

Inne odpowiedzi omawiają to, co robi Swift, ale nie wdawaj się w szczegóły, dlaczego.

Czy spodziewasz się, że" Å "będzie równe "Å"? Spodziewam się tego.

Jedna z nich to litera z kombinatorem, druga to pojedynczy złożony znak. Można dodać wiele różnych kombinatorów do postaci podstawowej, a człowiek nadal uważa ją za pojedynczą postać. Aby poradzić sobie z tego rodzaju rozbieżnością, stworzono pojęcie grafu, aby reprezentować to, co człowiek uważałby za postać niezależnie od użytych punktów kodowych.

Teraz wiadomości tekstowe od lat łączą znaki w graficzne emoji:). So various emoji were added to Unicode.
These services also started combining emoji together into composite emoji.
There of course is no reasonable way to encode all possible combinations into individual codepoints, so The Unicode Consortium decided to expand on the concept of graphemes to encompass these composite characters.

What this boils down to is "‍‍‍" powinien być traktowany jako pojedynczy "klaster grapheme", jeśli próbujesz pracować z nim na poziomie grapheme, tak jak Swift robi to domyślnie.

Jeśli chcesz sprawdzić, czy zawiera "" jako część tego, powinieneś zejść na niższy poziom.


Nie znam składni Swift więc tutaj jest jakiś Perl 6 który ma podobny poziom wsparcia dla Unicode.
(Perl 6 obsługuje Unicode w wersji 9, więc mogą występować rozbieżności)

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

Let ' s go down a 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
Zejście do tego poziomu może utrudnić pewne rzeczy.
my @match = "‍‍‍".ords;
my $l = @match.elems;
say @components.rotor( $l => 1-$l ).grep(@match).Bool; # True

Zakładam, że .contains W Swift To ułatwia, ale to nie znaczy, że nie ma innych rzeczy, które stają się trudniejsze.

Praca na tym poziomie znacznie ułatwia przypadkowe rozdzielenie ciągu znaków w środku złożonego dla przykład.


Nieumyślnie pytasz, dlaczego reprezentacja wyższego poziomu nie działa tak,jak reprezentacja niższego poziomu. Odpowiedź jest oczywiście taka, że nie powinna.

Jeśli zadajesz sobie pytanie "dlaczego to musi być tak skomplikowane ", odpowiedź brzmi oczywiście "ludzie ".

 16
Author: Brad Gilbert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-27 19:45:58

Swift 4.0 update

String otrzymuje wiele zmian w swift 4 update, jak udokumentowano w SE-0163 . Dwa emoji są używane do tego demo reprezentujące dwie różne struktury. Oba są połączone sekwencją emoji.

is the combination of two emoji, i

‍‍‍ jest kombinacją czterech Emotikon, o zerowej szerokości złącza. Format to ‍joiner‍joiner‍joiner

1. Liczy

W swift 4.0. emoji jest liczony jako klaster grapheme. Każda emotka jest zaliczony do 1. Właściwość count jest również bezpośrednio dostępna dla string. Więc możesz to bezpośrednio nazwać w ten sposób.

"".count  // 1. Not available on swift 3
"‍‍‍".count // 1. Not available on swift 3

Tablica znaków łańcucha jest również liczona jako klastry grafeme w swift 4.0, więc oba poniższe kody wypisują 1. Te dwa emotikony są przykładami sekwencji Emotikon, gdzie kilka Emotikon jest połączonych razem z lub bez joiner \u{200d} pomiędzy nimi. W swift 3.0 tablica znaków takiego ciągu oddziela każdy emotikon i daje tablicę z wiele elementów (emoji). Stolarka jest ignorowana w tym procesie. Jednak w swift 4.0, tablica znaków widzi wszystkie emoji jako jeden kawałek. Tak, że z każdego emoji zawsze będzie 1.

"".characters.count  // 1. In swift 3, this prints 2
"‍‍‍".characters.count // 1. In swift 3, this prints 4

unicodeScalars pozostaje bez zmian w swift 4. Zapewnia unikalne znaki Unicode w podanym ciągu.

"".unicodeScalars.count  // 2. Combination of two emoji
"‍‍‍".unicodeScalars.count // 7. Combination of four emoji with joiner between them

2. Zawiera

W swift 4.0 metoda contains ignoruje zerową szerokość joinera w emoji. Tak więc zwraca true dla dowolnego z czterech składników emoji "‍‍‍" i zwraca false, jeśli sprawdź stolarkę. Jednak w swift 3.0, joiner nie jest ignorowany i jest połączony z emoji przed nim. Jeśli więc sprawdzisz, czy "‍‍‍" zawiera pierwsze trzy komponenty emoji, wynik będzie fałszywy

"".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
 12
Author: Fangming,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-20 17:43:42