Advent of R: Dia 06

O Advent of Code é um Calendário do Advento desenvolvido por Eric Wastl composto por 25 pequenos exercícios de programação que vão sendo disponibilizados, um a um, entre 1º de dezembro e o Natal de cada ano.

Meu objetivo com o Advent of R é resolver todos os problemas do Advent of Code 2021 em R e documentar o processo através desta série de posts. Todo dia entre 01/12/2021 e 25/12/2021 eu vou tentar resolver o novo problema, documentar a minha solução aqui no blog e subir os meus scripts completos para um repositório público no GitHub.

A minha esperança é que, com essa série, mais pessoas pratiquem seus conhecimentos de R resolvendo exercícios divertidos e desafiadores! Ao final da jornada vamos todos ter afiado nossas habilidades de R e, quem sabe, divulgado essa linguagem incrível para mais pessoas. Boas festas e bom código!

Peixes-lanterna (A)

O dia 6 do AoC me pegou um pouco de surpresa. O primeiro item foi tranquilo de fazer: a entrada era uma lista de números que representavam os “contadores biológicos” de um cardume de peixes-lanterna e precisávamos retornar o número de peixes depois de 80 dias.

Os peixes adultos demoram 7 dias (contador vai de 6 até 0) para gerar um novo peixe bebê e um peixe bebê demora 9 dias (contador vai de 8 até 0) para gerar seu primeiro filhote.

Estado inicial   : 3,4,3,1,2
Depois de  1 dia : 2,3,2,0,1
Depois de  2 dias: 1,2,1,6,0,8
Depois de  3 dias: 0,1,0,5,6,7,8
Depois de  4 dias: 6,0,6,4,5,6,7,8,8
Depois de  5 dias: 5,6,5,3,4,5,6,7,7,8
Depois de  6 dias: 4,5,4,2,3,4,5,6,6,7
Depois de  7 dias: 3,4,3,1,2,3,4,5,5,6
Depois de  8 dias: 2,3,2,0,1,2,3,4,4,5
Depois de  9 dias: 1,2,1,6,0,1,2,3,3,4,8
Depois de 10 dias: 0,1,0,5,6,0,1,2,2,3,7,8
Depois de 11 dias: 6,0,6,4,5,6,0,1,1,2,6,7,8,8,8
Depois de 12 dias: 5,6,5,3,4,5,6,0,0,1,5,6,7,7,7,8,8
Depois de 13 dias: 4,5,4,2,3,4,5,6,6,0,4,5,6,6,6,7,7,8,8
Depois de 14 dias: 3,4,3,1,2,3,4,5,5,6,3,4,5,5,5,6,6,7,7,8
Depois de 15 dias: 2,3,2,0,1,2,3,4,4,5,2,3,4,4,4,5,5,6,6,7
Depois de 16 dias: 1,2,1,6,0,1,2,3,3,4,1,2,3,3,3,4,4,5,5,6,8
Depois de 17 dias: 0,1,0,5,6,0,1,2,2,3,0,1,2,2,2,3,3,4,4,5,7,8
Depois de 18 dias: 6,0,6,4,5,6,0,1,1,2,6,0,1,1,1,2,2,3,3,4,6,7,8,8,8,8

O meu código até que ficou bem simples. Precisei apenas de uma função que, todo dia, subtraia 1 de todos os contadores, criava 1 peixe com contador 8 para cada peixe com contador -1 e, por fim, subia todos os peixes com contador -1 para 6.

# Rodar n cíclos de reprodução do peixe-lanterna
reproduce <- function(fish, n = 80) {

  # Condição de parada
  if (n == 0) return(length(fish))

  # Reduzir contadores biológicos
  fish <- fish - 1L

  # Criar novos peixes e reiniciar contadores
  fish <- append(fish, rep_len(8L, length(fish[fish == -1L])))
  fish[fish == -1L] <- 6L

  # Recursão
  reproduce(fish, n = n - 1)
}

# Ler uma lista de peixes e reproduzir por 80 dias
"data-raw/06a_lanternfish.txt" |>
  readr::read_lines() |>
  stringr::str_split(",") |>
  purrr::pluck(1) |>
  as.integer() |>
  reproduce()
#> [1] 362666

Peixes-lanterna (B)

O segundo item do exercício não mudava essencialmente nada em relação ao primeiro. Assumindo espaço e recursos infinitos, quantos peixes teríamos depois de 256 dias?

Para resolver esse item, em teoria, seria necessário trocar apenas o valor do n por 256. Mas não foi o que aconteceu… Por causa da ineficiência do algoritmo, obter uma resposta demoraria horas e acabaria com a memória do meu computador. Foi necessário pensar em um novo método de resolver o problema.

A solução abaixo foi inspirada pela função table(). Para reduzir a exigência de espaço e não precisar iterar ao longo de um vetor com todos os peixes, eu agrupei os peixes com o mesmo contador biológico em apenas uma linha de uma tabela! Assim o programa nunca precisava lidar com mais de 9 linhas por dia, resolvendo as complicações com espaço e tempo.

# Rodar n cíclos de reprodução do peixe-lanterna
reproduce <- function(fish, n = 80) {

  # Condição de parada
  if (n == 0) return(sum(fish$n))

  # Reduzir contadores biológicos
  fish <- dplyr::mutate(fish, timer = timer - 1L)

  # Criar novos peixes
  babies <- fish |>
    dplyr::filter(timer == -1L) |>
    dplyr::mutate(timer = 8L)

  # Reiniciar contadores e recursão
  fish |>
    dplyr::bind_rows(babies) |>
    dplyr::mutate(timer = ifelse(timer == -1L, 6L, timer)) |>
    dplyr::group_by(timer) |>
    dplyr::summarise(n = sum(n)) |>
    reproduce(n = n - 1)
}

# Ler uma lista de peixes e reproduzir por 256 dias
"data-raw/06b_lanternfish.txt" |>
  readr::read_lines() |>
  stringr::str_split(",") |>
  purrr::pluck(1) |>
  as.integer() |>
  tibble::as_tibble() |>
  purrr::set_names("timer") |>
  dplyr::count(timer) |>
  reproduce(n = 256) |>
  format(scientific = FALSE)
#> [1] 1640526601595
comments powered by Disqus