Trabajar con datos binarios es un concepto importante en cualquier lenguaje, y Elixir proporciona una sintaxis elegante para escribir, hacer coincidir y construir datos binarios.
En Elixir, los datos binarios se conocen como el tipo bitstring. El tipo de datos binarios (que no debe confundirse con los datos binarios en general) es una forma específica de bitstring, que veremos en un ejercicio posterior.
Los literales de bitstring se definen utilizando la forma especial de bitstring <<>>. Al definir un literal de bitstring, este se define en segmentos. Cada segmento tiene un valor y un tipo, separados por el operador ::. El tipo especifica cuántos bits se utilizarán para codificar el valor. El tipo se puede omitir por completo, en cuyo caso se usará por defecto un valor entero de 8 bits.
# This defines a bitstring with three segments of a single bit each
<<0::1, 1::1, 0::1>>
Especificar el tipo como ::1 es una forma abreviada de escribir ::size(1). Tienes que usar la sintaxis más larga si el tamaño en bits proviene de una variable.
Al escribir literales de números enteros binarios, podemos escribirlos directamente en notación de base 2 anteponiendo 0b al literal. Ten en cuenta que, de todos modos, se mostrarán como números decimales al imprimirlos en los resultados de los tests o al usar iex.
<<0b1011::4>> == <<11::4>>
# => true
Si el valor del segmento desborda la capacidad del tipo del segmento, se truncará por la izquierda.
<<0b1011::3>> == <<0b0011::3>>
# => true
Puedes tanto añadir al principio como al final de un bitstring existente utilizando la forma especial. El tipo ::bitstring debe usarse en el bitstring existente si este es de tamaño desconocido.
value = <<0b110::3, 0b001::3>>
new_value = <<0b011::3, value::bitstring, 0b000::3>>
# => <<120, 8::size(4)>>
Podemos concatenar bitstrings almacenados en variables utilizando la forma especial. El tipo ::bitstring debe usarse al concatenar dos bitstrings de tamaños desconocidos.
first = <<0b110::3>>
second = <<0b001::3>>
concatenated = <<first::bitstring, second::bitstring>>
# => <<49::size(6)>>
La coincidencia de patrones también se puede usar para obtener valores de la forma especial. Tienes que conocer el número de bits de cada fragmento que quieras capturar, con una excepción: el tipo ::bitstring se puede usar para hacer coincidencia de patrones en un bitstring de tamaño desconocido, pero solo se puede usar para el último fragmento.
<<value::4, rest::bitstring>> = <<0b01101001::8>>
value == 0b0110
# => true
Es posible que los bitstrings se impriman (por el test runner o en iex) en un formato diferente al que se utilizó para crearlos. Esto suele causar confusión al aprender bitstrings.
Por defecto, los bitstrings se muestran en fragmentos de 8 bits (un byte), incluso si los creaste con fragmentos de un tamaño diferente.
<<2011::11>>
# => <<251, 3::size(3)>>
Si creas un bitstring que representa un string imprimible codificado en UTF-8, se muestra como un string.
<<>>
# => ""
<<65, 66, 67>>
# => "ABC"
Pintar por números (también conocido como colorear por números) son dibujos en blanco y negro pensados para colorear. Las distintas zonas del dibujo llevan anotados números diferentes, y esos números se corresponden con colores concretos de una paleta de colores predefinida. El objetivo es rellenar las zonas con los colores correctos, y así, al final, aparece un bonito dibujo lleno de color. Es una actividad relajante tanto para niños como para adultos.
Te han encargado escribir una aplicación de pintar por números en Elixir. Quieres que tu aplicación pueda importar y exportar dibujos en un formato de datos personalizado. Has decidido usar ficheros binarios para almacenar los datos de tus dibujos.
Este ejercicio da por supuesto que estás familiarizado con los números binarios y que entiendes los principios que hay detrás de convertir números binarios en números decimales y números decimales en binarios.
Imagina que tienes un dibujo de una cara sonriente, como la que se muestra a continuación. El dibujo tiene un fondo blanco. La cara sonriente tiene el borde negro y el relleno amarillo.
Este dibujo usa 3 colores. Supongamos que asignamos índices a esos colores:
0 (binario: 0b00) para el blanco,1 (binario: 0b01) para el negro,2 (binario: 0b10) para el amarillo.Ahora podemos usar esos índices de color para representar el color de cada píxel.
| Cara sonriente | Cara sonriente con índices de color |
|---|---|
![]() |
![]() |
Implementa la función PaintByNumber.palette_bit_size/1. Debe recibir el número de colores de la paleta y devolver cuántos bits son necesarios para representar esa cantidad de índices de color como números binarios. Los índices de color siempre empiezan en 0 y son enteros consecutivos que van en orden ascendente.
Por ejemplo, para representar 13 colores diferentes se necesitan 4 bits. 4 bits pueden almacenar hasta 16 índices de color (2^4). 3 bits no serían suficientes, porque 3 bits solo pueden almacenar hasta 8 índices de color (2^3).
PaintByNumber.palette_bit_size(13)
# => 4
Nota: no hay ninguna función log2 en la biblioteca estándar de Elixir. Más adelante aprenderás a usar las bibliotecas de Erlang desde Elixir, donde puedes encontrar esa función. Por ahora, resuelve esta tarea con recursión y el operador de potencia **.
Implementa la función PaintByNumber.empty_picture/0. Debe devolver un bitstring vacío.
Para probar manualmente las funcionalidades básicas de tu aplicación se usará un dibujo de prueba predefinido. El dibujo de prueba consta de 4 píxeles con 4 colores diferentes.
Implementa la función PaintByNumber.test_picture/0. Debe devolver un bitstring formado por 4 segmentos.
Cada segmento debe tener un tamaño de 2 bits. Los segmentos deben tener los valores 0, 1, 2 y 3.
Implementa la función PaintByNumber.prepend_pixel/3. Debe recibir tres argumentos: un bitstring con el dibujo al que vamos a anteponer, el número de colores de la paleta y el índice del color del nuevo píxel. Debe devolver un bitstring con el dibujo y el nuevo píxel añadido al principio.
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>>)
Implementa la función PaintByNumber.get_first_pixel/2. Debe recibir dos argumentos: un bitstring con el dibujo del que estamos leyendo y el número de colores de la paleta. Debe devolver el índice de color del primer píxel del dibujo dado. Cuando se le pasa un dibujo vacío, debe devolver nil.
picture = <<19::5, 2::5, 18::5>>
color_count = 20
PaintByNumber.get_first_pixel(picture, color_count)
# => 19
Implementa la función PaintByNumber.drop_first_pixel/2. Debe recibir dos argumentos: un bitstring con el dibujo del que vamos a quitar un píxel y el número de colores de la paleta. Debe devolver el dibujo sin el primer píxel. Cuando se le pasa un dibujo vacío, debe devolver un dibujo vacío.
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>>)
Implementa la función PaintByNumber.concat_pictures/2. Debe recibir dos argumentos, dos bitstrings. Debe devolver un bitstring que sea el resultado de anteponer el primer argumento al segundo.
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>>)
Regístrate en Exercism para aprender y dominar Elixir con 58 conceptos168 ejercicios y mentoría humana real, todo gratis.