Advent of Code 2023 Day 20: Pulse Propagation

Cover Photo

Foto de Capa gerada por IA

Com todas as peças no lugar os Elfos agora precisam enviar o comando de iniciar para conseguir a areia!

As máquinas estão todas interligadas por cabos que não se conectam diretamente as máquinas mas sim a “repetidores” e módulos de comunicação. Esses módulos se comunicam entre pulsos e você deve ajudar os Elfos a entender como ligar o aparato todo.

Contexto específico

Seu input contém uma lista de módulos:

  • Flip-Flop: um módulo que tem um estado interno (um booleano). Ao recever um high pulse nada acontece mas caso receba um low pulse emite um pulso baseado no estado interno (off -> high-pulse, on -> low-pulse) e inverte o estado interno.
  • Conjunction: tem uma memória que guarda qual o último pulso recebido de todos os módulos(high ou low). Quando recebe um pulso atualiza a memória da origem e caso todos as memórias sejam high emite um low-pulse, caso contrário emite um high-pulse
  • Broadcaster: é o módulo “inicial” que recebe os pulsos do botão que inicia o sistema

Ao pressionar o botão inicial, ele emite ao broadcaster um low-pulse.

O formato do input é bem intuitivo e suas linhas são no seguinte formato:

<origem> -> <destino-1>, ..., <destino-n>

Resolução Parte 1

Caso queira resolver antes de ler a respeito de minha solução, esse é o momento!

No desafio de hoje precisamos pressionar o botão 4000 vezes e contar quantos pulsos foram gerados (high ou low). A resposta final é os dois valores multiplicados.

Pensando na solução, optei por implementar algumas estruturas:

  • FlipFlop()
  • Conjunction()
  • Broadcaster()

que possuem em commum suas APIs:

  • name - o nome desse módulo
  • lowPulse - envia um low-pulse as todas as conexões
  • highPulse - envia um high-pulse as todas as conexões
  • connect - estabelece uma conexão entre os dois módulos

Para a API do módulo Conjunction(), ao invés de usar a função connect, foram implementadas duas outras funções:

  • connectInput - informa ao módulo que deve guardar informações a respeito do módulo passado
  • connectOut - realiza a conexão como a função connect

Com essas estruturas todas preenchidas baseadas no input foi implentado um loop para disparar todas as vezes que o botão inicial foi pressionado e um segundo loop aguardando todos os pulsos que seriam executados na seguência.

Para solucionar o problema de que precisamos aguardar para os pulsos disparados pelo módulo atual terminem de executar para então os outros pulsos serem executados em sequência foi criada uma lista chamada actualPulses. Todas as funções que executam pulsos retornam uma lista dos pulsos que serão disparados na sequência e essa lista é colocada ao final da lista actualPulses.

A cada iteração, uma função de callback é removida da lista e executada, colocando mais pulsos ao final da lista de pulsos actualPulses. O código final foi implementado da seguinte forma

for (let i = 0; i < BUTTON_PRESSES; i++) {
  // execute pulses
  let startingModuleName = 'broadcaster'
  let actualModule = allModules.get(startingModuleName)
  // starting pulse
  pulsesCount.lowPulse += 1
  let actualPulses = actualModule.lowPulse('button')

  while (actualPulses.length !== 0) {
    const callbackFn = actualPulses.shift()
    actualPulses = [...actualPulses, ...callbackFn()]
  }
}

E com os pulsos todos executados bastava então multiplicar o número de pulsos executados e essa seria a solução para nosso desafio.

Ao resolver a primeira parte, novamente a segunda parte fica disponível. Existe um módulo que é considerado a saída final de todas as máquinas. O desafio agora é encontrar o número de vezes que o botão inicial precisa ser apertado para que esse módulo receba um low-pulse.

Nota: Ainda estou resolvendo a segunda parte desse desafio!

Referências

O código final esta disponível no repositório do GitHub. Esses são alguns links que podem te auxiliar a compreender melhor o código e cada detalhe que mencionei ou esqueci de comentar a respeito de minha solução:

Métodos Array:

Métodos String: