Limpinho

Limpinho

Exercício de aprendizagem

Introdução

Este é um assunto grande! É possível escrever um livro longo sobre isso, e várias pessoas já o fizeram (pesquise "unicode book" na Amazon para ver alguns exemplos).

Uma breve história

Lidar com caracteres em computadores era bem mais simples nas décadas passadas, quando quem programava assumia que o inglês era a única língua importante. Então: 26 letras, maiúsculas e minúsculas, 10 dígitos, vários sinais de pontuação, além de um código (0x07) para tocar uma campainha, e tudo cabia em 7 bits: o conjunto de caracteres ASCII.

Naturalmente, as pessoas começaram a perguntar sobre à, ä e Ł, depois outras começaram a perguntar sobre ऄ, ஹ e ญ, e os jovens queriam emojis 😱. O que fazer?

Para encurtar a história, muitas pessoas inteligentes e pacientes tiveram que participar de comitês durante anos, definindo os detalhes do conjunto de caracteres Unicode e de codificações como UTF-8, e muito software precisou de uma reescrita muito complicada. Além disso, muitos bugs novos surgiram.

Para evitar que tudo quebrasse, o design do Unicode/UTF-8 garante que os primeiros 127 códigos sejam idênticos aos do ASCII (até a campainha).

Caracteres em Julia

Linguagens criadas depois de 2005 têm a enorme vantagem de que um padrão Unicode razoavelmente estável já existia.

Julia (lançada em 2012) pôde assumir que tudo seria Unicode: caracteres, strings, nomes de variáveis e funções, operadores matemáticos...

Citando o manual: "Julia torna simples e eficiente lidar com texto ASCII puro, e lidar com Unicode é o mais simples e eficiente possível." Repare no "possível", que é uma parte importante dessa afirmação.

Literais de caractere são escritos entre aspas simples e são diferentes de strings escritas entre aspas duplas.

Isso é óbvio para quem vem do mundo C/C++, mas pode confundir quem programa em Python e JavaScript.

julia> a = 'a'  # Roman alphabet
'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)

julia> typeof(a)
Char

julia> jha = 'झ'  # Devanagari alphabet
'झ': Unicode U+091D (category Lo: Letter, other)

julia> typeof(jha)
Char

julia> '❤'  # heart emoji
'❤': Unicode U+2764 (category So: Symbol, other)

Podemos ver pelos exemplos que o tipo é Char, e Julia traz mais informações sobre a categoria do caractere.

Olhando mais de perto, parece que esses caracteres podem ser representados por 4 dígitos hexadecimais. O conjunto completo de caracteres precisa de até 6 dígitos hexadecimais.

Esses números são chamados de "pontos de código" e atualmente vão de U+0000 a U+10FFFF. Eles aparecem no REPL, mas dentro do código use codepoints() para obtê-los.

Converter entre Char e Int é simples:

julia> Int('a')
97

julia> Char(97)
'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)

O compilador permite algumas formas de aritmética de inteiros com Chars:

julia> 'b' - 'a'  # interval, in alphabetic order
1

julia> 'b' + 'a'
ERROR: MethodError: no method matching +(::Char, ::Char)

julia> 'a' + 5
'f': ASCII/Unicode U+0066 (category Ll: Letter, lowercase)

julia> 'f' + ('A' - 'a')  # same as `uppercase('f')`
'F': ASCII/Unicode U+0046 (category Lu: Letter, uppercase)

Funções para caracteres

Muitas funções de manipulação de string também podem funcionar com uma entrada do tipo Char.

  • Para os alfabetos adequados, mude maiúsculas e minúsculas com uppercase() e lowercase().
  • Teste maiúsculas e minúsculas com isuppercase() e islowercase().
  • Teste o tipo de caractere com:
    • isletter(), que abrange muitos alfabetos
    • isdigit(), que testa estritamente 0:9
    • isnumeric(), mais amplo que isdigit, então retorna true para ¾ e vários sistemas de escrita não europeus
    • isxdigit(), dígitos hexadecimais
    • isascii(), caractere pré-Unicode
    • ispunct(), pontuação
    • isspace(), qualquer caractere de espaço em branco
    • isprint(), caracteres imprimíveis (o oposto é iscntrl())
islowercase('A')  # false
uppercase('γ')  # 'Γ': Unicode U+0393 (category Lu: Letter, uppercase)
ispunct('@')  # true
isdigit('A')  # false
isxdigit('A')  # true

Conversões entre Vector de Char e String

Para converter de String para Vector de Char, podemos usar collect().

De Vector de Char para String, existe o construtor String().

julia> s = "abcde"
"abcde"

julia> cv = collect(s)
5-element Vector{Char}:
 'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)
 'b': ASCII/Unicode U+0062 (category Ll: Letter, lowercase)
 'c': ASCII/Unicode U+0063 (category Ll: Letter, lowercase)
 'd': ASCII/Unicode U+0064 (category Ll: Letter, lowercase)
 'e': ASCII/Unicode U+0065 (category Ll: Letter, lowercase)

julia> String(cv)
"abcde"

Isso funciona com qualquer caractere, não só ASCII.

julia> collect("❤,😱")
3-element Vector{Char}:
 '❤': Unicode U+2764 (category So: Symbol, other)
 ',': ASCII/Unicode U+002C (category Po: Punctuation, other)
 '😱': Unicode U+1F631 (category So: Symbol, other)

Repare que o construtor String() opera sobre um Vector. Para converter um único Char em uma string de 1 caractere, a função é string(), com s minúsculo.

julia> string('a')
"a"

Armazenamento

Tudo neste documento parece relativamente simples até aqui. Então será que realmente não há muito com o que se preocupar?

Infelizmente, isso é otimista demais!

Uma complicação vem da necessidade de "até" 6 dígitos hexadecimais por ponto de código. Isso significa que caracteres diferentes precisam de quantidades diferentes de espaço na memória quando codificados em UTF-8.

Um byte só consegue armazenar números (sem sinal) até 255, dois dígitos hexadecimais, então o UTF-8 usa um número variável de bytes (de 1 a 4) para armazenar um Char. Eles são chamados de "unidades de código", e a função ncodeunits() retorna o número necessário para um dado caractere.

julia> codepoint(jha)  # jha 'झ' is defined in an earlier example
0x0000091d

julia> ncodeunits(jha)
3

julia> ncodeunits('a')  # ASCII character
1

julia> ncodeunits('😱')  # emoji
4

Além disso, nem tudo o que pode ser exibido na tela tem seu próprio ponto de código único. Alguns caracteres visualmente distintos são considerados derivados de outros, então o Unicode os trata como um caractere pai mais um modificador.

Esse problema afeta as strings, onde cria desafios para a indexação.

Instruções

Neste exercício você vai implementar um conjunto parcial de rotinas utilitárias para ajudar um desenvolvedor a limpar nomes de identificadores.

Nas 6 tarefas, você vai construir aos poucos as funções transform, para converter caracteres individuais, e clean, para converter strings.

Um identificador válido é composto de zero ou mais letras, sublinhados, hifens, pontos de interrogação e emojis.

Se uma string vazia for passada para a função clean, uma string vazia deve ser retornada.

1. Substitua os hifens encontrados por sublinhados

Implemente a função transform para substituir hifens por sublinhados.

julia> transform('-')
"_"

2. Remova todos os espaços em branco

Remova todos os caracteres de espaço em branco. Isso inclui espaços em branco no início e no fim.

julia> transform(' ')
""

3. Converta camelCase para kebab-case

Modifique a função transform para converter camelCase em kebab-case

julia> transform('D')
"-d"

4. Omita caracteres que são dígitos

Modifique a função transform para omitir qualquer caractere numérico.

julia> transform('7')
""

5. Substitua letras gregas minúsculas por pontos de interrogação

Modifique a função transform para substituir qualquer letra grega no intervalo de 'α' a 'ω'.

julia> transform('β')
"?"

6. Combine essas operações para atuar sobre uma string

Implemente a função clean para aplicar essas operações a uma string inteira.

Os caracteres que ficam fora das regras devem passar sem alteração.

julia> clean("  a2b Cd-ω😀  ")
"ab-cd_?😀"
Editar via GitHub O link abre em uma nova janela ou aba
Julia Exercism

Tudo pronto para começar Limpinho?

Crie sua conta no Exercism para aprender e dominar Julia com 35 conceitos128 exercícios e mentoria humana de verdade, tudo de graça.