Limpinho

Limpinho

Exercício de aprendizagem

Introdução

Este é um tema vasto! É possível escrever um livro comprido sobre ele, e várias pessoas já o fizeram (procura "unicode book" na Amazon para ver alguns exemplos).

Uma história muito breve

Trabalhar com carateres nos computadores era muito mais simples nas décadas anteriores, quando os programadores assumiam que o inglês era a única língua importante. Então: 26 letras, maiúsculas e minúsculas, 10 algarismos, vários sinais de pontuação, mais um código (0x07) para tocar uma campainha, e tudo cabia em 7 bits: o conjunto de carateres ASCII.

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

Para encurtar a história, muitas pessoas inteligentes e pacientes tiveram de integrar comités durante anos, a definir os detalhes do conjunto de carateres Unicode e de codificações como o UTF-8, e muito software precisou de uma reescrita muito complicada. Além disso, foram introduzidos muitos bugs novos.

Para evitar que tudo se avarie, a forma como o Unicode/UTF-8 foi concebido garante que os primeiros 127 códigos são idênticos ao ASCII (mesmo a campainha).

Carateres em Julia

As linguagens concebidas depois de 2005 têm a enorme vantagem de já existir um padrão Unicode razoavelmente estável.

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

Como diz o manual: "A Julia torna simples e eficiente lidar com texto ASCII comum, e tratar Unicode é tão simples e eficiente quanto possível." Repara no "quanto possível": é uma parte importante da afirmação.

Os literais de caráter escrevem-se entre aspas simples, e são distintos das strings escritas entre aspas duplas.

Isto é óbvio para quem vem do mundo C/C++, mas pode ser confuso para programadores de 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)

Vemos pelos exemplos que o tipo é Char, e que a Julia tem mais informação sobre a categoria do caráter.

Olhando com mais atenção, estes carateres podem ser representados por 4 algarismos hexadecimais. O conjunto completo de carateres precisa de até 6 algarismos hexadecimais.

Estes números chamam-se "pontos de código" e vão atualmente de U+0000 a U+10FFFF. Aparecem no REPL, mas dentro do código usa codepoints() para os obter.

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 carateres

Muitas funções que manipulam strings também funcionam com um Char.

  • Para os alfabetos adequados, converte entre maiúsculas e minúsculas com uppercase() e lowercase().
  • Verifica se está em maiúsculas ou minúsculas com isuppercase() e islowercase().
  • Testa o tipo de caráter com:
    • isletter(), abrange muitos alfabetos
    • isdigit(), testa estritamente 0:9
    • isnumeric(), mais abrangente do que isdigit, pelo que dá true para ¾ e para várias escritas não europeias
    • isxdigit(), algarismos hexadecimais
    • isascii(), caráter anterior ao Unicode
    • ispunct(), pontuação
    • isspace(), qualquer caráter de espaço em branco
    • isprint(), carateres 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 vetores de Char e strings

Para converter uma string num vetor de Char, podemos usar collect().

Para converter um vetor de Char numa 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"

Isto funciona com quaisquer carateres, não apenas com 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)

Repara que o construtor String() opera sobre um Vector. Para converter um único Char numa string de 1 caráter, a função é string(), com s minúsculo.

julia> string('a')
"a"

Armazenamento

Tudo o que vimos até aqui no documento parece relativamente simples. Então será que não há nada com que nos preocuparmos?

Infelizmente, isso é demasiado otimista!

Uma complicação vem da necessidade de "até" 6 algarismos hexadecimais por ponto de código. Isto significa que carateres diferentes precisam de quantidades diferentes de espaço em memória quando codificados em UTF-8.

Um byte só consegue armazenar números (sem sinal) até 255, dois algarismos hexadecimais, pelo que o UTF-8 usa um número variável de bytes (1 a 4) para armazenar um Char. Estes chamam-se "unidades de código", e a função ncodeunits() devolve o número necessário para um determinado caráter.

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 apresentado no ecrã tem o seu próprio ponto de código único. Alguns carateres visualmente distintos são considerados derivados de outros, pelo que o Unicode os trata como um caráter base mais um modificador.

Este problema afeta as strings, onde coloca desafios à indexação.

Instruções

Neste exercício vais implementar um conjunto parcial de rotinas utilitárias para ajudar um programador a limpar nomes de identificadores.

Ao longo das 6 tarefas vais construir gradualmente as funções transform, para converter carateres individuais, e clean, para converter strings.

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

Se for passada uma string vazia à função clean, deve ser devolvida uma string vazia.

1. Substitui os hífenes por sublinhados

Implementa a função transform para substituir os hífenes por sublinhados.

julia> transform('-')
"_"

2. Remove todos os espaços em branco

Remove todos os carateres de espaço em branco. Isto inclui espaços em branco no início e no fim.

julia> transform(' ')
""

3. Converte camelCase em kebab-case

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

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

4. Omite os carateres que são algarismos

Modifica a função transform para omitir os carateres que sejam numéricos.

julia> transform('7')
""

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

Modifica a função transform para substituir as letras gregas no intervalo de 'α' a 'ω'.

julia> transform('β')
"?"

6. Combina estas operações para atuar sobre uma string

Implementa a função clean para aplicar estas operações a uma string inteira.

Os carateres que não se enquadram nas regras devem passar inalterados.

julia> clean("  a2b Cd-ω😀  ")
"ab-cd_?😀"
Editar via GitHub A ligação abre numa nova janela ou separador
Julia Exercism

Estás pronto para começar Limpinho?

Inscreve-te no Exercism para aprenderes e dominares Julia com 35 conceitos128 exercícios, e mentoria humana real, tudo grátis.