Percursos
/
Elixir
Elixir
/
Exercícios
/
Pintar por números
Pintar por números

Pintar por números

Exercício de aprendizagem

Introdução

Bitstrings

Trabalhar com dados binários é um conceito importante em qualquer linguagem, e o Elixir oferece uma sintaxe elegante para escrever, corresponder e construir dados binários.

No Elixir, os dados binários são designados por tipo bitstring. O tipo de dados binários (que não deves confundir com dados binários em geral) é uma forma específica de bitstring, que vamos abordar num exercício mais adiante.

Os literais de bitstring definem-se com a forma especial de bitstring <<>>. Quando defines um literal de bitstring, este é definido em segmentos. Cada segmento tem um valor e um tipo, separados pelo operador ::. O tipo especifica quantos bits serão usados para codificar o valor. O tipo pode ser omitido por completo, e nesse caso assume um valor inteiro de 8 bits por predefinição.

# This defines a bitstring with three segments of a single bit each
<<0::1, 1::1, 0::1>>

Especificar o tipo como ::1 é uma forma abreviada de escrever ::size(1). Tens de usar a sintaxe mais longa se o tamanho em bits vier de uma variável.

Binário

Ao escrever literais de inteiros binários, podemos escrevê-los diretamente em notação de base 2, colocando o prefixo 0b antes do literal. Repara que, de qualquer forma, serão apresentados como números decimais quando forem impressos nos resultados dos testes ou quando usas o iex.

<<0b1011::4>> == <<11::4>>
# => true

Truncagem

Se o valor do segmento exceder a capacidade do tipo do segmento, é truncado a partir da esquerda.

<<0b1011::3>> == <<0b0011::3>>
# => true

Acrescentar ao início e ao fim

Podes acrescentar tanto ao início como ao fim de um bitstring existente usando a forma especial. O tipo ::bitstring tem de ser usado no bitstring existente se este tiver um tamanho desconhecido.

value = <<0b110::3, 0b001::3>>
new_value = <<0b011::3, value::bitstring, 0b000::3>>
# => <<120, 8::size(4)>>

Concatenar

Podemos concatenar bitstrings guardados em variáveis usando a forma especial. O tipo ::bitstring tem de ser usado quando concatenamos dois bitstrings de tamanhos desconhecidos.

first = <<0b110::3>>
second = <<0b001::3>>
concatenated = <<first::bitstring, second::bitstring>>
# => <<49::size(6)>>

Correspondência de padrões

A correspondência de padrões também pode ser usada para obter valores a partir da forma especial. Tens de saber o número de bits de cada fragmento que queres capturar, com uma exceção: o tipo ::bitstring pode ser usado para fazer correspondência de padrões num bitstring de tamanho desconhecido, mas só pode ser usado no último fragmento.

<<value::4, rest::bitstring>> = <<0b01101001::8>>
value == 0b0110
# => true

Inspecionar bitstrings

Note

Os bitstrings podem ser impressos (pelo test runner ou no iex) num formato diferente do formato que foi usado para os criar. Isto causa frequentemente confusão quando estás a aprender bitstrings.

Por predefinição, os bitstrings são apresentados em fragmentos de 8 bits (um byte), mesmo que os tenhas criado com fragmentos de um tamanho diferente.

<<2011::11>>
# => <<251, 3::size(3)>>

Se criares um bitstring que representa uma string imprimível codificada em UTF-8, é apresentado como uma string.

<<>>
# => ""

<<65, 66, 67>>
# => "ABC"

Instruções

Pintar por números (também conhecido como colorir por números) são imagens a preto e branco feitas para colorir. As diferentes áreas da imagem estão anotadas com números diferentes, e esses números correspondem a cores específicas numa paleta de cores predefinida. O objetivo é preencher as áreas com as cores certas, revelando no final uma bela imagem colorida. É uma atividade relaxante tanto para crianças como para adultos.

A tua tarefa é escrever uma aplicação de pintar por números em Elixir. Queres que a tua aplicação consiga importar e exportar imagens num formato de dados personalizado. Decidiste usar ficheiros binários para guardar os dados das tuas imagens.

Note

Este exercício assume que estás familiarizado com números binários e que compreendes os princípios por trás da conversão de números binários em números decimais e de números decimais em números binários.

Imagina que tens uma imagem de um smiley, como a que é mostrada abaixo. A imagem tem um fundo branco. O smiley tem um contorno preto e uma cor de preenchimento amarela.

Esta imagem usa 3 cores. Digamos que atribuímos índices a essas cores:

  • 0 (binário: 0b00) para o branco,
  • 1 (binário: 0b01) para o preto,
  • 2 (binário: 0b10) para o amarelo.

Podemos agora usar esses índices de cor para representar a cor de cada pixel.

Smiley Smiley com índices de cor

1. Calcular o tamanho em bits da paleta

Implementa a função PaintByNumber.palette_bit_size/1. Deve receber o número de cores da paleta e devolver quantos bits são necessários para representar essa quantidade de índices de cor como números binários. Os índices de cor começam sempre em 0 e são números inteiros consecutivos e crescentes.

Por exemplo, representar 13 cores diferentes requer 4 bits. 4 bits conseguem guardar até 16 índices de cor (2^4). 3 bits não seriam suficientes porque 3 bits só conseguem guardar até 8 índices de cor (2^3).

PaintByNumber.palette_bit_size(13)
# => 4

Nota: não existe nenhuma função log2 na biblioteca padrão do Elixir. Mais tarde vais aprender a usar bibliotecas do Erlang a partir do Elixir, onde podes encontrar esta função. Por agora, resolve esta tarefa com recursão e com o operador de potência **.

2. Criar uma imagem vazia

Implementa a função PaintByNumber.empty_picture/0. Deve devolver um bitstring vazio.

3. Criar uma imagem de teste

Uma imagem de teste predefinida será usada para testes manuais das funcionalidades básicas da tua aplicação. A imagem de teste é composta por 4 píxeis com 4 cores diferentes.

Implementa a função PaintByNumber.test_picture/0. Deve devolver um bitstring composto por 4 segmentos. Cada segmento deve ter um tamanho de 2 bits. Os segmentos devem ter os valores 0, 1, 2 e 3.

4. Adicionar um pixel ao início de uma imagem

Implementa a função PaintByNumber.prepend_pixel/3. Deve receber três argumentos: um bitstring com a imagem à qual estamos a adicionar ao início, o número de cores da paleta e o índice da cor do novo pixel. Deve devolver um bitstring com a imagem, com o novo pixel adicionado ao início.

picture = <<2::4, 0::4>>
color_count = 13
pixel_color_index = 11

PaintByNumber.prepend_pixel(picture, color_count, pixel_color_index)
# => <<178, 0::size(4)>>
# (which is equal to <<11::4, 2::4, 0::4>>)

5. Obter o primeiro pixel de uma imagem

Implementa a função PaintByNumber.get_first_pixel/2. Deve receber dois argumentos: um bitstring com a imagem da qual estamos a ler e o número de cores da paleta. Deve devolver o índice de cor do primeiro pixel da imagem dada. Quando receber uma imagem vazia, deve devolver nil.

picture = <<19::5, 2::5, 18::5>>
color_count = 20

PaintByNumber.get_first_pixel(picture, color_count)
# => 19

6. Remover o primeiro pixel de uma imagem

Implementa a função PaintByNumber.drop_first_pixel/2. Deve receber dois argumentos: um bitstring com a imagem da qual estamos a remover um pixel e o número de cores da paleta. Deve devolver a imagem sem o primeiro pixel. Quando receber uma imagem vazia, deve devolver uma imagem vazia.

picture = <<2::3, 5::3, 5::3, 0::3>>
color_count = 6

PaintByNumber.drop_first_pixel(picture, color_count)
# => <<180, 0::size(1)>>
# (which is equal to <<5::3, 5::3, 0::3>>)

7. Concatenar duas imagens

Implementa a função PaintByNumber.concat_pictures/2. Deve receber dois argumentos, dois bitstrings. Deve devolver um bitstring que é o resultado de adicionar o primeiro argumento ao início do segundo argumento.

picture1 = <<52::6, 51::6>>
picture2 = <<0::6, 34::6, 12::6>>

PaintByNumber.concat_pictures(picture1, picture2)
# => <<211, 48, 34, 12::size(6)>>
# (which is equal to <<52::6, 51::6, 0::6, 34::6, 12::6>>)
Editar via GitHub A ligação abre numa nova janela ou separador
Elixir Exercism

Estás pronto para começar Pintar por números?

Inscreve-te no Exercism para aprenderes e dominares Elixir com 58 conceitos168 exercícios, e mentoria humana real, tudo grátis.