Resolución de retos de AdventJS (retos 5 al 8)

15 min

12/21/2025

Resolución de retos de AdventJS (retos 5 al 8)

En este artículo quiero compartir mi resolución de los retos de AdventJS 2025 comparándola con la solución de la IA y aprendiendo sobre como ir mejorando cada reto.


Reto 5 - FÁCIL

Enunciado

Los elfos tienen un timestamp secreto: es la fecha y hora exacta en la que Papá Noel despega con el trineo 🛷 para repartir regalos por el mundo. Pero en el Polo Norte usan un formato rarísimo para guardar la hora: YYYY*MM*DD@HH|mm|ss NP (ejemplo: 2025*12*25@00|00|00 NP).

Tu misión es escribir una función que reciba dos parámetros:

  • fromTime → fecha de referencia en formato elfo (YYYY*MM*DD@HH|mm|ss NP).

  • takeOffTime → la misma fecha de despegue, también en formato elfo. La función debe devolver:

  • Los segundos completos que faltan para el despegue.

  • Si ya estamos en el despegue exacto → 0.

  • Si el despegue ya ocurrió → un número negativo indicando cuántos segundos han pasado desde entonces.

🎯 Reglas

  • Convierte el formato elfo a un timestamp primero. El sufijo NP indica la hora oficial del Polo Norte (sin husos horarios ni DST), así que puedes tratarlo como si fuera UTC.
  • Usa diferencias en segundos, no en milisegundos.
  • Redondea siempre hacia abajo (floor): solo segundos completos.

Solución

En mi solución, cree una función auxiliar para convertir el formato elfo a formato UTC y a partir de ahí calcular la diferencia en segundos.

En la solución de la IA, utiliza match para extraer los datos de la fecha y hora, y también validar si el formato proporcionado es correcto. A partir de ahí, utiliza Date.UTC para calcular la diferencia en segundos.

// MI SOLUCIÓN:
function timeUntilTakeOff(fromTime, takeOffTime) {
  // Función auxiliar para convertir el formato elfo a formato UTC
  // Entrada: '2025*12*25@00|00|00 NP'
  // Salida:  '2025-12-25T00:00:00Z' (La Z fuerza que sea UTC/NP)
  const parseElfTime = time => {
    return time
      .replace(/\*/g, '-') // Reemplaza * por -
      .replace('@', 'T') // Reemplaza @ por T
      .replace(/\|/g, ':') // Reemplaza | por :
      .replace(' NP', 'Z') // Reemplaza ' NP' por Z (UTC)
  }

  // Convertimos las fechas a objetos Date reales
  const fromDate = new Date(parseElfTime(fromTime))
  const takeOffDate = new Date(parseElfTime(takeOffTime))

  // Calculamos la diferencia en milisegundos
  const differenceMs = takeOffDate - fromDate

  // Convertimos a segundos y redondeamos hacia abajo
  return Math.floor(differenceMs / 1000)
}

// SOLUCIÓN IA:
function timeUntilTakeOff(fromTime: string, takeOffTime: string): number {
  const parseElfTimeToSeconds = (time: string): number => {
    const match = time.match(
      /^(\d{4})\*(\d{2})\*(\d{2})@(\d{2})\|(\d{2})\|(\d{2}) NP$/
    )
    // Validamos que el formato elfo sea correcto
    if (!match) {
      throw new Error('Formato elfo inválido')
    }
    // Desestructuramos el array de números con los datos correspondientes
    const [, year, month, day, hour, min, sec] = match.map(Number)

    // Date.UTC devuelve milisegundos desde epoch en UTC
    return Math.floor(Date.UTC(year, month - 1, day, hour, min, sec) / 1000)
  }

  const fromSeconds = parseElfTimeToSeconds(fromTime)
  const takeOffSeconds = parseElfTimeToSeconds(takeOffTime)

  return takeOffSeconds - fromSeconds
}

// EJEMPLOS:
const takeoff = '2025*12*25@00|00|00 NP'

// desde el 24 diciembre 2025, 23:59:30, 30 segundos antes del despegue
timeUntilTakeOff('2025*12*24@23|59|30 NP', takeoff)
// 30

// justo en el momento exacto
timeUntilTakeOff('2025*12*25@00|00|00 NP', takeoff)
// 0

// 12 segundos después del despegue
timeUntilTakeOff('2025*12*25@00|00|12 NP', takeoff)
// -12

Reto 6 - FÁCIL

Enunciado

En el taller de Santa, los elfos han encontrado una montaña de guantes mágicos totalmente desordenados. Cada guante viene descrito por dos valores:

  • hand: indica si es un guante izquierdo (L) o derecho (R)
  • color: el color del guante (string) Tu tarea es ayudarles a emparejar guantes: Un par válido es un guante izquierdo y uno derecho del mismo color.

Debes devolver una lista con los colores de todos los pares encontrados. Ten en cuenta que puede haber varios pares del mismo color. El orden se determina por el que se pueda hacer primero el par.

Solución

En mi solución, utilizo un objeto para almacenar los guantes que están esperando pareja. Luego hago una iteración por cada guante y verifico si existe un guante del color y mano opuesta esperando. Si existe, añado el color al array de pares y resto el guante que estaba esperando. Si no existe, añado el guante a la lista de esperando.

En la solución de la IA, no utilizó un contador para saber si hay guantes esperando, sino que utilizó un Set por mano para saber si existe uno para emparejar inmediatamente. Esta no es una solución correcta ya que si hay dos guantes del mismo color y mano esperando, sólo se emparejará uno de ellos en caso de que exista otro guante del color y mano opuesta esperando.

type Glove = { hand: 'L' | 'R'; color: string }

// MI SOLUCIÓN:
function matchGloves(gloves: Glove[]): string[] {
  const pairs = []
  // Este objeto guardará los guantes que están esperando pareja
  // Estructura: { "color": { L: cantidad, R: cantidad } }
  const waitingGloves = {}

  for (const { hand, color } of gloves) {
    // matchHand: mano opuesta
    const matchHand = hand === 'L' ? 'R' : 'L'

    // 1. Verificamos si existe un guante del color y mano opuesta esperando
    if (waitingGloves[color] && waitingGloves[color][matchHand] > 0) {
      pairs.push(color)
      waitingGloves[color][matchHand]-- // Restamos el guante que estaba esperando
    } else {
      // 2. En el caso de que no haya pareja, añadimos el guante a waintingGloves
      if (!waitingGloves[color]) {
        // En caso de que no exista el objeto lo creamos
        waitingGloves[color] = { L: 0, R: 0 }
      }
      // Y le añadimos una unidad
      waitingGloves[color][hand]++
    }
  }

  return pairs
}

// SOLUCIÓN IA:
function matchGloves(gloves: Glove[]): string[] {
  const pairs: string[] = []

  const waiting = {
    L: new Set<string>(),
    R: new Set<string>()
  }

  for (const { hand, color } of gloves) {
    const opposite = hand === 'L' ? 'R' : 'L'

    if (waiting[opposite].has(color)) {
      pairs.push(color)
      waiting[opposite].delete(color)
    } else {
      waiting[hand].add(color)
    }
  }

  return pairs
}

// EJEMPLOS:
const gloves = [
  { hand: 'L', color: 'red' },
  { hand: 'R', color: 'red' },
  { hand: 'R', color: 'green' },
  { hand: 'L', color: 'blue' },
  { hand: 'L', color: 'green' }
]

matchGloves(gloves)
// ["red", "green"]

const gloves2 = [
  { hand: 'L', color: 'gold' },
  { hand: 'R', color: 'gold' },
  { hand: 'L', color: 'gold' },
  { hand: 'L', color: 'gold' },
  { hand: 'R', color: 'gold' }
]

matchGloves(gloves2)
// ["gold", "gold"]

const gloves3 = [
  { hand: 'L', color: 'red' },
  { hand: 'R', color: 'green' },
  { hand: 'L', color: 'blue' }
]

matchGloves(gloves3)
// []

const gloves4 = [
  { hand: 'L', color: 'green' },
  { hand: 'L', color: 'red' },
  { hand: 'R', color: 'red' },
  { hand: 'R', color: 'green' }
]

matchGloves(gloves4)
// ['red', 'green']

Reto 7 - MEDIO

Enunciado

¡Es hora de decorar el árbol de Navidad 🎄! Escribe una función que reciba:

  • height → la altura del árbol (número de filas).
  • ornament → el carácter del adorno (por ejemplo, “o” o ”@”).
  • frequency → cada cuántas posiciones de asterisco aparece el adorno. El árbol se dibuja con asteriscos *, pero cada frequency posiciones, el asterisco se reemplaza por el adorno.

El conteo de posiciones empieza en 1, desde la copa hasta la base, de izquierda a derecha. Si frequency es 2, los adornos aparecen en las posiciones 2, 4, 6, etc.

El árbol debe estar centrado y tener un tronco # de una línea al final. Cuidado con los espacios en blanco, nunca hay al final de cada línea.

Solución

En mi solución, realicé un bucle para ir creando cada línea del árbol, y otro bucle para ir creando cada carácter de la línea, validando si corresponde el adorno o un asterisco.

En la solución de la IA, las líneas del árbol se crean en un array y se unen al final con join('\n'), lo que hace que el código sea más limpio y fácil de entender, la solución quedó más expresiva.

// MI SOLUCIÓN:
function drawTree(height: number, ornament: string, frequency: number): string {
  let ornamentTurn = 1
  let tree = ''

  for (let i = 1; i <= height; i++) {
    // Creo la línea de la fila actual
    let treeLine = `${' '.repeat(height - i)}`
    for (let j = 0; j < i * 2 - 1; j++) {
      // Si el número de adornos es igual a la frecuencia, añado un adorno
      if (ornamentTurn === frequency) {
        ornamentTurn = 1
        treeLine += ornament
      } else {
        ornamentTurn++
        treeLine += '*'
      }
    }
    // Añado la línea al árbol al finalizar la iteración
    tree += `${treeLine}\n`
  }
  // Finalmente añado el tronco
  tree += `${' '.repeat(height - 1)}#`
  return tree
}

// SOLUCIÓN IA:
function drawTree(height: number, ornament: string, frequency: number): string {
  let position = 0
  const lines: string[] = []

  for (let i = 1; i <= height; i++) {
    const spaces = ' '.repeat(height - i)
    const width = i * 2 - 1

    let row = ''

    for (let j = 0; j < width; j++) {
      position++
      row += position % frequency === 0 ? ornament : '*'
    }

    lines.push(spaces + row)
  }

  lines.push(' '.repeat(height - 1) + '#')

  return lines.join('\n')
}

// EJEMPLOS:
drawTree(5, 'o', 2)
//     *
//    o*o
//   *o*o*
//  o*o*o*o
// *o*o*o*o*
//     #

drawTree(3, '@', 3)
//   *
//  *@*
// *@**@
//   #

drawTree(4, '+', 1)
//    +
//   +++
//  +++++
// +++++++
//    #

Reto 8 - FÁCIL

Enunciado

Santa 🎅 quiere saber cuál es la primera letra no repetida en el nombre de un juguete 🎁.

Escribe una función que reciba un string y devuelva la primera letra que no se repite, ignorando mayúsculas y minúsculas al contar, pero devolviendo la letra tal como aparece en el string.

Si no hay ninguna, devuelve una cadena vacía ("").

Solución

En mi solución, recorrí cada caracter del string con un for, y luego validé si no existía en el array, si no existía, lo devolvia.

En la solución de la IA, resuelve un problema de complejidad innecesaria de mi solución (O(n^2)), ya que dentro del for realizaba un bucle adicional (toyArray.some(…)) por cada iteración, lo que hace que la solución sea mucho más lenta. En cambio, la solución de la IA hace dos pasadas O(n), una para contar las letras y otra para encontrar la primera no repetida, lo que hace que la solución sea mucho más rápida.

// MI SOLUCIÓN:
function findUniqueToy(toy: string): string {
  const toyArray = toy.split('')

  for (let index = 0; index < toyArray.length; index++) {
    if (
      !toyArray.some(
        (letter, i) =>
          letter.toLowerCase() === toyArray[index].toLowerCase() && i !== index
      )
    ) {
      return toyArray[index]
    }
  }
  return ''
}

// SOLUCIÓN IA:
function findUniqueToy(toy: string): string {
  const count: Record<string, number> = {}

  // Primera pasada: contar letras (case-insensitive)
  for (const char of toy) {
    const key = char.toLowerCase()
    count[key] = (count[key] ?? 0) + 1
  }

  // Segunda pasada: encontrar la primera no repetida
  for (const char of toy) {
    if (count[char.toLowerCase()] === 1) {
      return char
    }
  }

  return ''
}

// EJEMPLOS:
findUniqueToy('Gift') // 'G'
// ℹ️ La G es la primera letra que no se repite
// y la devolvemos tal y como aparece

findUniqueToy('sS') // ''
// ℹ️ Las letras se repiten, ya que no diferencia mayúsculas

findUniqueToy('reindeeR') // 'i'
// ℹ️ La r se repite (aunque sea en mayúscula)
// y la e también, así que la primera es la 'i'

// Más casos:
findUniqueToy('AaBbCc') // ''
findUniqueToy('abcDEF') // 'a'
findUniqueToy('aAaAaAF') // 'F'
findUniqueToy('sTreSS') // 'T'
findUniqueToy('z') // 'z'