Advent of R: Dia 10

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!

Pontuação de Sintaxe (A)

O dia 10 do AoC pedia para que resolvessemos um clássico problema de parentização com alguns facilitadores. Em resumo, recebíamos uma string composta por parênteses e seus amigos (“(”, “[”, ”{”, ”<”, ”>”, ”}”, ”]”, “)”) e precisávamos identificar se o fechamento de algum deles estava errado, por exemplo, “[}”, “{()()()>”, etc. Para cada string que tivesse um fechamento ilegal recebia uma quantidade de pontos de acordo com a tabela abaixo e, finalmente, a saída do exercício era a soma de todas as pontuações.

# ): 3 pontos.
# ]: 57 pontos.
# }: 1197 pontos.
# >: 25137 pontos.

Para quem nunca viu um problema desse tipo, a solução pode ser alcançada facilmente usando uma pilha. Cada caractere que abre um bloco é colocado na pilha e, para cada caractere que fecha um bloco, removemos o elemento do topo da pilha. Se os caracteres se complementam corretamente o algoritmo segue em frente, caso contrário ele busca a pontuação na tabela e retorna.

# Correspondência de valores
scores <- list(
  ")" = 3,
  "]" = 57,
  "}" = 1197,
  ">" = 25137
)

# Calcular a pontuação por caractere ilegal em uma linha
score_ilegal <- function(line) {
  stack <- flifo::lifo()

  # Iterar na linha até um elemento não corresponder
  symbols <- stringr::str_split(line, "")[[1]]
  for (symbol in symbols) {

    # Empilhar ou desempilhar (e calcular pontuação se necessário)
    if (symbol %in% c("(", "[", "{", "<")) {
      flifo::push(stack, symbol)
    } else {
      check <- flifo::pop(stack)
      if (
        (check == "{" && symbol != "}") ||
        (check == "(" && symbol != ")") ||
        (check == "[" && symbol != "]") ||
        (check == "<" && symbol != ">")
      ) {
        return(scores[names(scores) == symbol][[1]])
      }
    }
  }

  return(0)
}

# Iterar nas linhas e calcular pontuações
"data-raw/10a_syntax_scoring.txt" |>
  readr::read_lines() |>
  purrr::map_dbl(score_ilegal) |>
  sum()
#> [1] 216297

Pontuação de Sintaxe (B)

O segundo item do problema pedia para que começássemos removendo as linhas que tinham pontuação maior que 0 (então só foi necessário filtrar isso no código, que vou omitir). Depois disso o objetivo era completar as linhas que restavam.

O fato é que as linhas restantes estavam todas com um pedaço faltando, por exemplo, “[({(<(())[]>[[{[]{<()<>>” precisa ainda de ”}}]])})]” para ficar correta. Usando a lógica do item anterior, só precisávamos seguir o mesmo roteiro e, ao final da linha, contar os pontos dos caracteres que ainda haviam sobrado na pilha.

Desta vez a regra de pontuação era diferente: para cada caractere faltante, precisávamos multiplicar a pontuação corrente por 5 e então somar o valor do caractere de acordo com uma nova tabelha. A resposta final era a mediana da pontuação de todoas as linhas. Enfim, o código vai a seguir:

# Ler linhas e remover corrompidas
lines <- readr::read_lines("data-raw/10b_syntax_scoring.txt")
lines <- lines[purrr::map_dbl(lines, score_ilegal) == 0]

# Correspondência de valores
scores <- list(
  "(" = 1,
  "[" = 2,
  "{" = 3,
  "<" = 4
)

# Calcular a pontuação por caractere faltante em uma linha
score_complete <- function(line) {
  stack <- flifo::lifo()

  # Iterar na linha e remover parte completa
  symbols <- stringr::str_split(line, "")[[1]]
  for (symbol in symbols) {

    # Empilhar ou desempilhar
    if (symbol %in% c("(", "[", "{", "<")) {
      flifo::push(stack, symbol)
    } else {
      flifo::pop(stack)
    }
  }

  # Iterar no resto da pilha e calcular pontos
  score <- 0
  while (flifo::size(stack) > 0) {
    check <- flifo::pop(stack)
    score <- (score * 5) + scores[names(scores) == check][[1]]
  }

  return(score)
}

# Pegar mediana das pontuações
lines |>
  purrr::map_dbl(score_complete) |>
  median()
#> [1] 2165057169
comments powered by Disqus