Cómo convertir código RGB en código hexadecimal con R
En este post vamos a ver cómo escribir una función que convierta un código RGB en un código hexadecimal en R, como parte de un desafío de Codewars. Y, de paso, vamos a intentar entender distintos acercamientos para resolverlo.

Hace unos días que estoy jugando a resolver los desafíos de Codewars. Me parece una gran forma de practicar programación; si bien a veces se puede repetitivo (desarmar una cadena, aplicar una transformación, volver a formar una cadena), tengo que decir que me ayudó a flexibilizar muchísimo mi forma de pensar, además de ayudarme a aprender nuevas funciones.
Ahora, Codewars tiene algo que es bueno y malo al mismo tiempo: luego de presentar tu solución (exitosa) al desafío, podés ver las distintas formas en que las demás personas lo resolvieron. Y esto es buenísimo porque, justamente, podés ver distintos acercamientos al mismo problema, pero también es malo porque te encontrás con gente mucho más inteligente que resuelve en dos o tres líneas de código algo que a vos te llevó diez. En fin, parte del aprendizaje.
En general venía sobrellevando bien esta dualidad, pero el desafío de hoy me costó. La consigna pedía escribir una función que tomara como argumentos los tres códigos RGB de un determinado color y devolviera el código hexadecimal. Primer desafío: yo no tenía idea de cómo se armaba un hexcode, a pesar de usarlos hace años, así que le dediqué un largo rato, gracias a este sitio para entenderlo. No fue nada fácil. En resumen: tomamos cada uno de esos códigos y lo dividimos por 16, lo cual nos da dos números, el entero y el resto. Si alguno de esos números es mayor o igual a 10, se utilizan letras desde la A (10) hasta la F (16 en adelante). Además, la consigna indica que se puede presentar cualquier número pero que se lo debe redondear a números válidos: entonces, todos los números negativos se deberían redondear a 0 y todos los mayores a 255 se deberían redondear a 255. Esto lo descubrí después de varios intentos fallidos.
Entre una cosa y otra, estuve unas dos horas con este desafío, mucho más de lo que me han llevado otros. Una vez que lo logré, envié mi solución y… ¡qué decepción encontrar que había soluciones de dos líneas! Consideré la posibilidad de abandonar todo y dedicarme a otra cosa, pero pensé qué le diría yo a un estudiante frustrado en una situación así: convertilo en una situación de aprendizaje. Así que acá estamos: el objetivo de este post es mostrar mi código y cómo lo pensé, y luego revisar algunas de las soluciones alternativas para intentar aprender de ellas. ¡Adelante!
Mi respuesta
rgb <- function(r, g, b){
result <- list()
rgb <- c(r, g, b)
for (i in rgb){
if (i < 0){
num <- "00"
result <- append(result, num)
} else if (i > 255){
num <- "FF"
result <- append(result, num)
} else {
int <- round(i%/%16)
rem <- round(i%%16)
for (i in c(int, rem)){
num <- dplyr::case_when(
i < 10 & i >= 0 ~ as.character(i),
i < 0 ~ "00",
i == 10 ~ "A",
i == 11 ~ "B",
i == 12 ~ "C",
i == 13 ~ "D",
i == 14 ~ "E",
i == 15 ~ "F",
i >= 16 ~ "F"
)
result <- append(result, num)}
}
}
return(paste(unlist(result), collapse = ""))
}Básicamente, la función puede dividirse en los siguientes pasos:
La primera línea prepara una lista donde se van a almacenar los resultados. Necesitamos que sea una lista porque vamos a trabajar con iteraciones.
La segunda línea construye un elemento que almacena los tres códigos que el usuario brinda por separado.
Abrimos un for loop (si no te acordás qué es, en este post podés refrescarlo). Por cada uno de los elementos (los tres códigos provistos por el usuario), realizá lo siguiente:
Si es menor a 0, lo redondeamos a 0, con lo cual el resultado de la conversión (denominado
num) siempre va a resultar en “00”. Guardamos ese resultado en el objetoresult.Si no es menor a 0, pero es mayor a 255, lo redondeamos a 255, con lo cual el resultado de la conversión siempre va a resultar en “FF”. Sumamos ese resultado al objeto
result.Si no se cumple ninguna de las condiciones (es decir, es mayor o igual a 0 y menor a 10), entonces ahora sí podemos aplicar la conversión hexadecimal. Las dos condiciones anteriores estaban pensadas para descartar estos dos casos atípicos y asegurarnos de trabajar solo con valores válidos.
En este tercer caso, lo que hago es dividir el número por 16 usando división entera (
%/%) para obtener el primer dígito del hexadecimal, y usar el resto (%%) para obtener el segundo. Cada uno de esos valores puede ir de 0 a 15, por lo que necesito traducirlos a su representación correspondiente: números del 0 al 9 o letras de la A a la F.Para eso abro un segundo
for, que itera sobre el entero y el resto, y usocase_when()para mapear cada valor a su representación hexadecimal. Cada resultado se va agregando al objetoresult, que al final de todo el proceso contiene seis caracteres (dos por cada canal RGB). Finalmente, los uno en una sola cadena conpaste(..., collapse = "")y devuelvo ese valor como resultado de la función.
¿Es un código largo? Sí. ¿Es el más elegante? Probablemente no. ¿Funciona y refleja exactamente cómo estaba pensando el problema? Definitivamente. Y eso último, para mí, no es menor.
Otras formas de resolver el problema
Una de las cosas más interesantes de Codewars es ver cómo otras personas resolvieron exactamente el mismo desafío con enfoques muy distintos. En mi caso, después de enviar mi solución, me encontré con códigos mucho más cortos que el mío, y eso fue inicialmente frustrante. Pero, mirándolos con un poco más de calma, también fue muy formativo.
1. Usar funciones que ya hacen parte del trabajo
Una de las primeras cosas que aprendí revisando otras soluciones es que R ya sabe convertir números a hexadecimal. La función as.hexmode() hace exactamente eso: toma un número decimal y devuelve su representación hexadecimal.
Una solución bastante común era algo como esto:
rgb <- function(r, g, b) {
paste0(
toupper(
sprintf(
"%02x",
pmax(pmin(c(r, g, b), 255), 0)
)
),
collapse = ""
)
}Esta versión hace varias cosas interesantes:
c(r, g, b)agrupa los tres valores en un vector.pmin()ypmax()se usan para “recortar” los valores al rango permitido (0–255), sin necesidad deif.sprintf("%02x", ...)convierte cada número a hexadecimal y se asegura de que tenga siempre dos caracteres.toupper()pone las letras en mayúscula, como exige la consigna.paste0(..., collapse = "")arma el resultado final.
Todo eso… en una sola expresión. Cuando vi algo así por primera vez pensé: “jamás se me hubiera ocurrido”. Y es cierto. Pero justamente por eso vale la pena mirarlo con atención: no para compararse, sino para ampliar el repertorio de herramientas posibles.
2. Pensar en términos vectoriales (y no paso a paso) Mi solución está pensada de forma muy secuencial: primero este caso, después este otro, después este cálculo, después esta traducción. Muchas de las soluciones más cortas, en cambio, están pensadas desde una lógica más vectorial, aprovechando que R trabaja naturalmente con vectores y que muchas funciones ya están optimizadas para eso.
Esto no significa que una forma sea “mejor” que la otra en abstracto. Depende mucho de en qué momento del aprendizaje estés y de qué estés intentando entender. En mi caso, necesitaba desarmar el problema hasta el mínimo detalle para poder comprenderlo.
¿Odié o amé este proceso?
Después de pasar por este desafío, me quedé pensando que Codewars no es solo un espacio para “entrenar” programación, sino también para entrenar la tolerancia a la frustración (y para esto tenemos bastante experiencia). Ver soluciones extremadamente concisas puede ser desalentador, pero también puede ser una invitación a leer código ajeno con curiosidad en lugar de con culpa.
Mi solución no es la más corta, ni la más elegante, ni la más idiomática en R. Pero es una solución que puedo explicar línea por línea, y eso sigue siendo un criterio importante.
Si estás empezando a programar (o incluso si no), mi recomendación es esta: primero escribí el código que entiendas, aunque sea largo. Después, cuando el problema ya esté resuelto, recién ahí mirá cómo lo hicieron otras personas. Ahí es donde el aprendizaje se vuelve realmente interesante.
P.D.: parece que no me inventé la mejora de los LLM en este último tiempo: en este tuit Andrej Karpathy, dice algo similar.