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 un bitstring, que veremos en un ejercicio posterior.
Los literales de bitstring se definen usando la forma especial de bitstring <<>>. Cuando se define un literal de bitstring, se define en segmentos. Cada segmento tiene un valor y un tipo, separados por el operador ::. El tipo especifica cuántos bits se usarán para codificar el valor. El tipo se puede omitir por completo, y por defecto será 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). Necesitas usar la sintaxis más larga si el tamaño de bits proviene de una variable.
Al escribir literales de enteros binarios, podemos escribirlos directamente en notación en base 2 si anteponemos 0b al literal. Ten en cuenta que de todos modos se mostrarán como números decimales cuando se impriman en los resultados de las pruebas o cuando uses iex.
<<0b1011::4>> == <<11::4>>
# => true
Si el valor del segmento desborda la capacidad del tipo del segmento, se truncará desde la izquierda.
<<0b1011::3>> == <<0b0011::3>>
# => true
Puedes agregar tanto al principio como al final de un bitstring existente usando la forma especial. El tipo ::bitstring debe usarse en el bitstring existente si 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 usando la forma especial. El tipo ::bitstring se debe usar cuando se concatenan 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 hacer para obtener valores de la forma especial. Debes saber la cantidad 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 esto 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 ejecutor de pruebas o en iex) en un formato diferente al que se usó para crearlos. Esto a menudo causa confusión al aprender bitstrings.
De forma predeterminada, 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 mostrará como un string.
<<>>
# => ""
<<65, 66, 67>>
# => "ABC"
Pintar por números (también conocido como colorear por números) son imágenes en blanco y negro pensadas para colorear. Las distintas áreas de la imagen están anotadas con números diferentes, y los números corresponden a colores específicos de una paleta de colores predefinida. El objetivo es rellenar las áreas con los colores correctos, revelando al final una hermosa imagen llena 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 imágenes en un formato de datos personalizado. Has decidido usar archivos binarios para almacenar los datos de tus imágenes.
Este ejercicio asume que conoces los números binarios y que entiendes los principios detrás de convertir números binarios a decimales y números decimales a binarios.
Imaginemos que tienes una imagen de una carita sonriente, como la que se muestra a continuación. La imagen tiene un fondo blanco. La carita sonriente tiene un borde negro y un color de relleno amarillo.
Esta imagen usa 3 colores. Digamos 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.
| Carita sonriente | Carita sonriente con índices de color |
|---|---|
![]() |
![]() |
Implementa la función PaintByNumber.palette_bit_size/1. Debe recibir la cantidad 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 ascendentes continuos.
Por ejemplo, representar 13 colores diferentes requiere 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 una 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.
Se usará una imagen de prueba predefinida para probar manualmente las funcionalidades básicas de tu aplicación. La imagen de prueba consta de 4 píxeles con 4 colores diferentes.
Implementa la función PaintByNumber.test_picture/0. Debe devolver un bitstring que consta de 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 la imagen a la que vamos a anteponer, la cantidad de colores de la paleta y el índice del color del nuevo píxel. Debe devolver un bitstring con una imagen a la que se agregó el nuevo píxel 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 la imagen de la que estamos leyendo y la cantidad de colores de la paleta. Debe devolver el índice de color del primer píxel de la imagen dada. Cuando recibe una imagen vacía, 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 la imagen de la que estamos quitando un píxel y la cantidad de colores de la paleta. Debe devolver la imagen sin el primer píxel. Cuando recibe una imagen vacía, debe devolver una imagen vacía.
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.