O personagem 👩👩👧👦 (família com duas mulheres, uma garota e um menino) é codificado da seguinte forma:
U+1F469
WOMAN
,
U+200D
ZWJ
,
U+1F469
WOMAN
,
U+200D
ZWJ
,
U+1F467
GIRL
,
U+200D
ZWJ
,
U+1F466
BOY
Portanto, é muito interessante codificado; o alvo perfeito para um teste de unidade. No entanto, Swift parece não saber como tratá-lo. Aqui está o que eu quero dizer:
"👩👩👧👦".contains("👩👩👧👦") // true
"👩👩👧👦".contains("👩") // false
"👩👩👧👦".contains("\u{200D}") // false
"👩👩👧👦".contains("👧") // false
"👩👩👧👦".contains("👦") // true
Então, Swift diz que se contém (bom) e um menino (bom!). Mas, então, diz que não contém uma mulher, menina ou marceneiro de largura zero. O que está acontecendo aqui? Por que Swift sabe que contém um menino, mas não uma mulher ou menina? Eu podia entender se o tratava como um único personagem e o reconhecia apenas contendo a si mesmo, mas o fato de ter um subcomponente e nenhum outro me deixa perplexo.
Isso não muda se eu usar algo parecido "👩".characters.first!
.
Ainda mais confuso é o seguinte:
let manual = "\u{1F469}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}"
Array(manual.characters) // ["👩", "👩", "👧", "👦"]
Embora eu tenha colocado os ZWJs lá, eles não são refletidos na matriz de caracteres. O que se seguiu foi um pouco revelador:
manual.contains("👩") // false
manual.contains("👧") // false
manual.contains("👦") // true
Então, eu recebo o mesmo comportamento com a matriz de caracteres ... o que é extremamente irritante, pois sei como é a matriz.
Isso também não muda se eu usar algo parecido "👩".characters.first!
.
"👩👩👧👦".contains("\u{200D}")
ainda retorna false, não tenho certeza se isso é um bug ou recurso.Respostas:
Isso tem a ver com a forma como o
String
tipo funciona no Swift e como ocontains(_:)
método funciona.O '👩👩👧👦' é conhecido como uma sequência de emoji, que é renderizada como um caractere visível em uma string. A sequência é composta de
Character
objetos e, ao mesmo tempo, é composta deUnicodeScalar
objetos.Se você verificar a contagem de caracteres da sequência, verá que ela é composta de quatro caracteres, enquanto que, se você verificar a contagem escalar unicode, mostrará um resultado diferente:
Agora, se você analisar os caracteres e imprimi-los, verá o que parece ser caracteres normais, mas, na verdade, os três primeiros caracteres contêm um emoji e um marceneiro de largura zero
UnicodeScalarView
:Como você pode ver, apenas o último caractere não contém um marceneiro de largura zero; portanto, ao usar o
contains(_:)
método, ele funciona conforme o esperado. Como você não está comparando com emoji contendo marcadores de largura zero, o método não encontrará uma correspondência para nenhum, exceto o último caractere.Para expandir isso, se você criar um
String
que é composto de um caractere emoji que termina com um marceneiro de largura zero e passá-lo para ocontains(_:)
método, ele também avaliaráfalse
. Isso tem a ver comcontains(_:)
ser exatamente o mesmo querange(of:) != nil
, que tenta encontrar uma correspondência exata para o argumento fornecido. Como os caracteres que terminam com um marceneiro de largura zero formam uma sequência incompleta, o método tenta encontrar uma correspondência para o argumento enquanto combina os caracteres que terminam com marceneiros de largura zero em uma sequência completa. Isso significa que o método nunca encontrará uma correspondência se:Para demonstrar:
No entanto, como a comparação apenas olha para o futuro, é possível encontrar várias outras sequências completas na sequência trabalhando de trás para frente:
A solução mais fácil seria fornecer uma opção de comparação específica para o
range(of:options:range:locale:)
método. A opçãoString.CompareOptions.literal
executa a comparação com uma equivalência exata de caractere por caractere . Como observação, o significado de caractere aqui não é o SwiftCharacter
, mas a representação UTF-16 da instância e da string de comparação - no entanto, comoString
não permite UTF-16 malformado, isso é essencialmente equivalente à comparação do escalar Unicode. representação.Aqui eu sobrecarreguei o
Foundation
método, portanto, se você precisar do original, renomeie este ou algo assim:Agora, o método funciona como "deveria" com cada caractere, mesmo com sequências incompletas:
fonte
"👩👩👧👦".count
avalia-se1
com o Xcode 9 beta atual e o Swift 4. #O primeiro problema é que você está fazendo uma ponte para a Foundation
contains
(a SwiftString
não é aCollection
), então esse é umNSString
comportamento, que eu não acredito que lida com Emoji composto tão poderosamente quanto Swift. Dito isso, acredito que a Swift esteja implementando o Unicode 8 no momento, que também precisava de revisão em torno dessa situação no Unicode 10 (portanto, tudo isso pode mudar quando eles implementam o Unicode 10; eu não procurei ou não).Para simplificar, vamos nos livrar do Foundation e usar o Swift, que fornece visualizações mais explícitas. Começaremos com caracteres:
ESTÁ BEM. É o que esperávamos. Mas é mentira. Vamos ver o que esses personagens realmente são.
Ah ... então é
["👩ZWJ", "👩ZWJ", "👧ZWJ", "👦"]
. Isso deixa tudo um pouco mais claro. 👩 não é membro desta lista (é "👩ZWJ"), mas 👦 é um membro.O problema é que
Character
é um "cluster grafema", que compõe as coisas juntas (como anexar o ZWJ). O que você realmente está procurando é um escalar unicode. E isso funciona exatamente como você está esperando:E, é claro, também podemos procurar o personagem real que está lá:
(Isso duplica fortemente os pontos de Ben Leggiero. Publiquei isso antes de perceber que ele havia respondido. Partindo caso seja mais claro para alguém.)
fonte
ZWJ
significa?String
foi supostamente alterado novamente para um tipo de coleção. Isso afeta sua resposta?Parece que Swift considera
ZWJ
a um grupo de grafemas estendido com o personagem imediatamente anterior a ele. Podemos ver isso ao mapear a matriz de caracteres paraunicodeScalars
:Isso imprime o seguinte no LLDB:
Além disso, os
.contains
grupos estenderam grupos de grafemas em um único caractere. Por exemplo, tendo os caráteres de Hangulᄒ
,ᅡ
, eᆫ
(que se combinam para tornar a palavra coreana para "um":한
):Não foi possível encontrar
ᄒ
porque os três pontos de código estão agrupados em um cluster que atua como um caractere. Da mesma forma,\u{1F469}\u{200D}
(WOMAN
ZWJ
) é um cluster, que atua como um caractere.fonte
As outras respostas discutem o que Swift faz, mas não entram em muitos detalhes sobre o porquê.
Você espera que "Å" seja igual a "Å"? Eu espero que você faça.
Uma delas é uma carta com um combinador, a outra é um caractere composto único. Você pode adicionar muitos combinadores diferentes a um personagem base, e um humano ainda consideraria um único personagem. Para lidar com esse tipo de discrepância, o conceito de grafema foi criado para representar o que um humano consideraria um personagem, independentemente dos pontos de código usados.
Agora, os serviços de mensagens de texto combinam caracteres em emoji gráfico há anos
:)
→🙂
. Então, vários emoji foram adicionados ao Unicode.Esses serviços também começaram a combinar emoji emoji composto.
É claro que não há uma maneira razoável de codificar todas as combinações possíveis em pontos de código individuais, portanto, o Unicode Consortium decidiu expandir o conceito de grafemas para abranger esses caracteres compostos.
O que isso se resume a isso
"👩👩👧👦"
deve ser considerado como um único "cluster de grafema" se você tentar trabalhar com ele no nível do grafema, como o Swift faz por padrão.Se você quiser verificar se ele contém
"👦"
parte disso, desça para um nível mais baixo.Eu não conheço a sintaxe do Swift, então aqui estão alguns Perl 6 que têm nível de suporte semelhante para Unicode.
(Perl 6 suporta Unicode versão 9, portanto, pode haver discrepâncias)
Vamos descer um nível
Descer a este nível pode dificultar algumas coisas.
Presumo que
.contains
em Swift torne isso mais fácil, mas isso não significa que não há outras coisas que se tornam mais difíceis.Trabalhar nesse nível facilita muito a divisão acidental de uma sequência no meio de um caractere composto, por exemplo.
O que você está perguntando inadvertidamente é por que essa representação de nível superior não funciona como uma representação de nível inferior. A resposta é claro, não deveria.
Se você está se perguntando " por que isso tem que ser tão complicado ", a resposta é obviamente " humanos ".
fonte
rotor
egrep
faz aqui? E o que é1-$l
?rotor
,. O códigosay (1,2,3,4,5,6).rotor(3)
produz((1 2 3) (4 5 6))
. Essa é uma lista de listas, cada comprimento3
.say (1,2,3,4,5,6).rotor(3=>-2)
produz o mesmo, exceto que a segunda sub-lista começa com2
e não4
a terceira com3
e assim por diante((1 2 3) (2 3 4) (3 4 5) (4 5 6))
. Se@match
contiver"👩👩👧👦".ords
, o código de @ Brad cria apenas uma sub-lista; portanto, o=>1-$l
bit é irrelevante (não utilizado). Só é relevante se@match
for menor que@components
.grep
tenta corresponder a cada elemento em seu invocante (nesse caso, uma lista de sublistas de@components
). Ele tenta corresponder cada elemento ao seu argumento de correspondência (neste caso,@match
). Em.Bool
seguida, retornaTrue
segrep
produz pelo menos uma correspondência.Atualização do Swift 4.0
A String recebeu muitas revisões na atualização do Swift 4, conforme documentado no SE-0163 . Dois emoji são usados para esta demonstração, representando duas estruturas diferentes. Ambos são combinados com uma sequência de emoji.
👍🏽
é a combinação de dois emoji👍
e🏽
👩👩👧👦
é a combinação de quatro emojis, com o marceneiro de largura zero conectado. O formato é👩joiner👩joiner👧joiner👦
1. Conta
No Swift 4.0, os emojis são contados como cluster de grafema. Cada emoji é contado como 1. A
count
propriedade também está disponível diretamente para a sequência. Então você pode chamá-lo diretamente assim.A matriz de caracteres de uma sequência também é contada como agrupamentos de grafema no Swift 4.0, portanto, os dois códigos a seguir imprimem 1. Esses dois emoji são exemplos de sequências de emoji, em que vários emoji são combinados com ou sem marcadores de largura zero
\u{200d}
entre eles. No swift 3.0, a matriz de caracteres dessa string separa cada emoji e resulta em uma matriz com vários elementos (emoji). O marceneiro é ignorado nesse processo. No entanto, no Swift 4.0, a matriz de caracteres vê todos os emoticons como uma peça. Assim, o de qualquer emoji será sempre 1.unicodeScalars
permanece inalterado no Swift 4. Ele fornece os caracteres Unicode exclusivos na string especificada.2. Contém
No Swift 4.0, o
contains
método ignora o marceneiro de largura zero em emoji. Portanto, ele retorna true para qualquer um dos quatro componentes emoji de"👩👩👧👦"
e retorna false se você verificar o marceneiro. No entanto, no Swift 3.0, o marceneiro não é ignorado e é combinado com o emoji à sua frente. Portanto, quando você verifica se"👩👩👧👦"
contém os três primeiros emojis componentes, o resultado será falsofonte
Emojis, assim como o padrão unicode, são enganosamente complicados. Tons de pele, sexo, trabalhos, grupos de pessoas, sequências de marcadores com largura zero, sinalizadores (unicode de 2 caracteres) e outras complicações podem tornar a análise de emoji confusa. Uma árvore de Natal, uma fatia de pizza ou uma pilha de cocô podem ser representadas com um único ponto de código Unicode. Sem mencionar que, quando novos emojis são introduzidos, há um atraso entre o suporte ao iOS e a liberação de emoji. Isso e o fato de que diferentes versões do iOS suportam versões diferentes do padrão unicode.
TL; DR. Eu trabalhei nesses recursos e abri uma biblioteca. Sou o autor do JKEmoji para ajudar a analisar as seqüências de caracteres com emojis. Torna a análise tão fácil quanto:
Ele faz isso atualizando rotineiramente um banco de dados local de todos os emojis reconhecidos na versão unicode mais recente ( 12.0 recentemente) e fazendo uma referência cruzada com o que é reconhecido como um emoji válido na versão do SO em execução, observando a representação de bitmap de um caractere emoji não reconhecido.
NOTA
Uma resposta anterior foi excluída por anunciar minha biblioteca sem afirmar claramente que sou o autor. Estou reconhecendo isso novamente.
fonte