[swift] 왜 👩‍👩‍👧‍👦과 같은 이모 지 문자가 Swift 문자열에서 그렇게 이상하게 취급됩니까?

캐릭터 👩‍👩‍👧‍👦 (여성 2 명, 소녀 1 명, 소년 1 명)은 다음과 같이 인코딩됩니다.

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

매우 흥미롭게 인코딩되었습니다. 단위 테스트를위한 완벽한 목표. 그러나 Swift는 그것을 치료하는 방법을 모릅니다. 여기 내가 의미하는 바가있다 :

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

따라서 Swift는 자체 (좋은)와 소년 (좋은!)을 포함하고 있다고 말합니다. 그러나 그것은 여자, 여자 또는 폭이 0 인 결합자를 포함하지 않는다고 말합니다. 여기서 무슨 일이야? 왜 Swift는 남자는 있지만 여자 나 여자는 포함하지 않는다고 알고 있습니까? 그것이 하나의 문자로 취급하고 자신을 포함하고있는 것을 인식했다면 이해할 수 있었지만 하나의 하위 구성 요소가 있고 다른 구성 요소가 없다는 사실이 나를 방해합니다.

내가 같은 것을 사용하면 변경되지 않습니다 "👩".characters.first!.


더 혼란스러운 것은 이것입니다.

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

ZWJ를 거기에 배치했지만 문자 배열에는 반영되지 않습니다. 다음은 약간의 이야기였습니다.

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

따라서 배열이 어떻게 보이는지 알고 있기 때문에 문자 배열과 동일한 동작을 얻습니다.

내가 같은 것을 사용하면 이것도 바뀌지 않습니다 "👩".characters.first!.



답변

이것은 StringSwift 에서 유형이 작동하는 contains(_:)방식 및 방법이 작동 하는 방식과 관련이 있습니다.

‘👩‍👩‍👧‍👦’은 이모 지 시퀀스로 알려진 것으로 문자열에서 하나의 보이는 문자로 렌더링됩니다. 시퀀스는 Character객체 로 구성 되며 동시에 UnicodeScalar객체 로 구성 됩니다.

문자열의 문자 수를 확인하면 4 개의 문자로 구성되며 유니 코드 스칼라 수를 확인하면 다른 결과가 표시됩니다.

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

이제 문자를 파싱하고 인쇄하면 일반 문자처럼 보이지만 실제로는 첫 번째 세 문자에는 이모티콘과 너비가 0 인 조이너가 모두 포함됩니다 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"]

보시다시피, 마지막 문자에만 너비가 0 인 결합자가 포함되어 있지 않으므로 contains(_:)메서드를 사용할 때 예상대로 작동합니다. 너비가 0 인 결합자를 포함하는 이모티콘과 비교하지 않기 때문에이 방법은 마지막 문자 이외의 일치 항목을 찾지 않습니다.

이를 확장하기 위해 String너비가 0 인 조이너로 끝나는 이모티콘 문자로 구성된를 만들고 contains(_:)메서드에 전달하면로 평가됩니다 false. 이 함께 할 수있다 contains(_:)으로 동일한되는 range(of:) != nil, 지정된 인수와 정확히 일치를 찾으려고 시도한다. 너비가 0 인 결합 자로 끝나는 문자는 불완전한 시퀀스를 형성하기 때문에이 메서드는 너비가 0 인 결합 자로 끝나는 문자를 완전한 시퀀스로 결합하는 동안 인수와 일치하는 항목을 찾으려고 시도합니다. 즉, 다음과 같은 경우 메소드가 일치하는 것을 찾지 않습니다.

  1. 인수는 너비가 0 인 결합 자로 끝납니다.
  2. 구문 분석 할 문자열에 불완전한 시퀀스가 ​​포함되어 있지 않습니다 (예 : 폭이 0 인 조인으로 끝나고 호환되는 문자가 아닌).

시연하려면 :

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

그러나 비교는 단지 앞선 것이므로 문자열에서 거꾸로 작업하면 몇 가지 다른 완전한 시퀀스를 찾을 수 있습니다.

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

가장 쉬운 해결책은 range(of:options:range:locale:)분석법에 특정 비교 옵션을 제공하는 것 입니다. 이 옵션 String.CompareOptions.literal정확한 문자 별 비교를 수행합니다 . 여기에 문자로 의미 무슨 보조 노트로서 하지 스위프트 Character하지만, 인스턴스와 비교 문자열 모두의 UTF-16 표현 – 이후하지만, String하지 기형, 이것은 기본적으로 유니 코드 스칼라를 비교하는 것과 UTF-16을 허용하지 대표.

여기에 Foundation메소드를 오버로드 했으므로 원래 메소드가 필요하면 이름을 바꾸십시오.

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

이제이 방법은 불완전한 시퀀스에서도 각 문자와 “제대로”작동합니다.

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


답변

첫 번째 문제는 contains(Swift ‘s Stringis not with)를 사용하여 Foundation에 브리징하는 것 Collection입니다.이 NSString동작은 Swift만큼 강력하게 Emoji로 구성된 핸들을 믿지 않습니다. 즉, Swift는 현재 유니 코드 8을 구현하고 있으며 유니 코드 10 에서이 상황에 대한 수정이 필요하다고 생각합니다 (따라서 유니 코드 10을 구현하면 모든 것이 바뀔 수 있습니다.

일을 단순화하기 위해 Foundation을 제거하고보다 명확한 뷰를 제공하는 Swift를 사용하십시오. 우리는 문자로 시작할 것입니다 :

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

확인. 그것이 우리가 기대 한 것입니다. 그러나 그것은 거짓말입니다. 그 캐릭터들이 실제로 무엇인지 봅시다.

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

아… 그래서 ["👩ZWJ", "👩ZWJ", "👧ZWJ", "👦"]. 그것은 모든 것을 조금 더 명확하게 만듭니다. 👩은 (는)이 목록의 회원이 아닙니다 ( “👩ZWJ”).

문제는 Character“grapheme cluster”인데, 이는 ZWJ를 연결하는 것과 같이 사물을 함께 구성합니다. 실제로 검색하는 것은 유니 코드 스칼라입니다. 그리고 그것은 당신이 기대하는대로 정확하게 작동합니다 :

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

물론 실제 존재하는 캐릭터도 찾을 수 있습니다.

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

(이것은 Ben Leggiero의 요점을 크게 복제 한 것입니다. 나는 그가 대답했다는 것을 알기 전에 이것을 게시했습니다. 누군가에게 더 분명한 경우를 대비하여 떠나십시오.)


답변

Swift는 a ZWJ가 바로 앞에있는 문자를 가진 확장 된 grapheme 클러스터로 간주 합니다. 문자 배열을 다음에 매핑 할 때 이것을 볼 수 있습니다 unicodeScalars.

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

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}"

또한 .contains확장 된 grapheme 클러스터를 단일 문자로 그룹화합니다. 예를 들어, 한글 문자 , (을 사용하여 한국어 단어 “one”:)을 작성하는 경우 한:

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

세 개의 코드 포인트가 하나의 문자로 작동하는 하나의 클러스터로 그룹화되어 있기 때문에이를 찾을 수 없습니다 . 마찬가지로 \u{1F469}\u{200D}( WOMAN ZWJ)는 하나의 문자로 작동하는 하나의 클러스터입니다.


답변

다른 답변은 Swift의 기능에 대해 설명하지만 그 이유에 대해서는 자세히 다루지 않습니다.

“Å”가“Å”과 같을 것으로 예상하십니까? 나는 당신이 기대합니다.

이 중 하나는 결합기가있는 문자이고 다른 하나는 단일 문자로 구성됩니다. 기본 캐릭터에 여러 가지 조합을 추가 할 수 있으며 인간은 여전히 ​​단일 캐릭터로 간주합니다. 이러한 종류의 불일치를 처리하기 위해, 사용 된 코드 포인트에 관계없이 인간이 캐릭터를 고려할 것을 나타내는 그래 핀 개념이 만들어졌습니다.

이제 문자 메시지 서비스는 몇 년 동안 문자를 그래픽 이모티콘으로 결합 해 왔습니다 :) →  🙂. 그래서 다양한 이모티콘이 유니 코드에 추가되었습니다.
이 서비스는 또한 이모티콘을 합성 이모티콘으로 결합하기 시작했습니다.
물론 가능한 모든 조합을 개별 코드 포인트로 인코딩하는 합리적인 방법은 없으므로 유니 코드 컨소시엄은 이러한 복합 문자를 포함하도록 그래 핀 개념을 확장하기로 결정했습니다.

이것이되어 귀결 "👩‍👩‍👧‍👦"스위프트가 기본적으로 수행 된대로 자소 수준에서 함께 작동하도록하려는 경우 하나의 “그래 핀 클러스터”로 고려되어야한다.

"👦"그 일부로 포함되어 있는지 확인하려면 더 낮은 수준으로 내려 가야합니다.


Swift 구문을 모르므로 여기에 비슷한 수준의 유니 코드 지원 펄 6이 있습니다.
(Perl 6은 유니 코드 버전 9를 지원하므로 불일치가있을 수 있습니다)

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

레벨을 내려 가자

# 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

이 수준으로 내려 가면 일부 작업이 더 어려워 질 수 있습니다.

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

.containsSwift에서 더 쉽게 만들 수 있다고 가정 하지만 더 어려운 다른 것이 없다는 것을 의미하지는 않습니다.

이 레벨에서 작업하면 예를 들어 복합 문자의 중간에 실수로 문자열을 분리하기가 훨씬 쉽습니다.


부주의하게 요구하는 것은이 높은 수준의 표현이 낮은 수준의 표현처럼 작동하지 않는 이유입니다. 대답은 물론 아닙니다.

왜 이것이 그렇게 복잡해야합니까? ”라고 스스로에게 물으면 대답은 물론“ 인간 ”입니다.


답변

스위프트 4.0 업데이트

SE-0163에 문서화 된 바와 같이, Swift 4 업데이트에서 String은 많은 수정을 받았습니다 . 이 데모에는 두 개의 다른 구조를 나타내는 두 개의 이모지가 사용됩니다. 둘 다 일련의 이모티콘과 결합됩니다.

👍🏽두 이모티콘의 조합 👍이며🏽

👩‍👩‍👧‍👦너비가 0 인 결합자가 연결된 4 개의 이모 지 조합입니다. 형식은👩‍joiner👩‍joiner👧‍joiner👦

1. 카운트

Swift 4.0에서 그림 이모티콘은 grapheme 클러스터로 계산됩니다. 모든 단일 이모지는 1로 계산됩니다.이 count속성은 문자열에도 직접 사용할 수 있습니다. 이렇게 직접 호출 할 수 있습니다.

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

문자열의 문자 배열도 Swift 4.0에서 grapheme 클러스터로 계산되므로 다음 두 코드가 모두 1로 인쇄됩니다.이 두 개의 이모지는 이모 지 시퀀스의 예이며 여러 이모지가 너비가 0 인 조이 너티와 함께 ​​또는없이 결합 \u{200d}됩니다. swift 3.0에서 이러한 문자열의 문자 배열은 각 이모지를 분리하고 여러 요소가있는 배열 (이모 지)을 생성합니다. 이 프로세스에서는 결합자가 무시됩니다. 그러나 Swift 4.0에서 문자 배열은 모든 이모티콘을 한 조각으로 간주합니다. 따라서 모든 이모 지의 이모티콘은 항상 1입니다.

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

unicodeScalars Swift 4에서는 변경되지 않습니다. 주어진 문자열에서 고유 한 유니 코드 문자를 제공합니다.

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

2. 포함

Swift 4.0 contains에서이 방법은 이모티콘에서 너비가 0 인 결합자를 무시합니다. 따라서의 4 개 이모티콘 구성 요소 중 하나에 대해 true를 반환 "👩‍👩‍👧‍👦"하고 결합자를 확인하면 false를 반환합니다. 그러나 Swift 3.0에서는 결합자가 무시되지 않고 앞의 그림 이모티콘과 결합됩니다. 따라서 "👩‍👩‍👧‍👦"처음 세 가지 구성 요소 이모티콘이 포함되어 있는지 확인하면 결과가 거짓이됩니다.

"👍🏽".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 인 결합 자 시퀀스, 플래그 (2 문자 유니 코드) 및 기타 복잡한 문제로 인해 이모티콘 파싱이 지저분해질 수 있습니다. 크리스마스 트리, 피자 조각 또는 더미 더미는 모두 단일 유니 코드 코드 포인트로 표현할 수 있습니다. 새로운 이모지가 소개 될 때 iOS 지원과 이모 지 릴리스 사이에 지연이 있다는 것은 말할 것도 없습니다. 그리고 다른 버전의 iOS는 다른 버전의 유니 코드 표준을 지원합니다.

TL; DR. 나는이 기능을 연구하고 문자열을 이모티콘으로 구문 분석하는 데 도움이 되는 JKEmoji 의 저자 인 라이브러리를 제공했습니다 . 다음과 같이 쉽게 구문 분석합니다.

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

5

최신 유니 코드 버전 ( 최근 12.0) 으로 인식 된 모든 이모 지의 로컬 데이터베이스를 정기적으로 새로 고치고 실행중인 OS 버전에서 유효한 이모 지로 인식되는 것과 로컬 참조를 비트 맵으로 표시하여 상호 참조합니다. 인식 할 수없는 이모티콘 문자

노트

내가 저자임을 명확하게 밝히지 않고 내 도서관 광고에 대한 이전 답변이 삭제되었습니다. 나는 이것을 다시 인정하고있다.


답변