Advent of code 2024 - Dia 08 de 25
Conteúdo
O oitavo dia do Advent of Code foi ontem 08/12/2024! Se você caiu aqui hoje pela primeira vez, não deixe de ler os posts sobre o Dia 1, Dia 2, Dia 3, Dia 4, Dia 5, Dia 6 e Dia 7.
Classifiquei o oitavo problema como fácil (parte 1) e fácil (parte 2).
Dicas#
-
O problema também utiliza uma espécie de labirinto para representar o mapa.
-
Se você considerar cada par de antenas como pontos, pode imaginar uma linha que passa entre eles.
-
Se você tem as posições de duas antenas como vetores, a diferença entre suas coordenadas contém o vetor que representa a linha.
-
Você pode achar outros pontos na mesma linha, subtraindo e adicionando o vetor a pontos que já conhece na mesma linha.
-
A parte 1 é basicamente criar a lista de anti-sinais e contar quantos são únicos.
-
Na parte 2, você pode prolongar a linha, criando vários pontos com um loop. Não esqueça de interromper o loop assim que as coordenadas começarem a ultrapassar os limites do mapa.
Quanto de Python você precisa saber?#
Os capítulos se referem ao meu livro Introdução à Programação com Python.
- Listas, conjuntos, dicionários - Capítulo 6
- Classes - Capítulo 10
- itertools - Capítulo 8
- Um pouco de álgebra linear ou conceitos básicos de vetores
Resposta#
from itertools import combinations
from auxiliares import (
string_para_matriz,
Vector2d,
Labirinto,
le_arquivo_sem_linhas_em_branco,
)
ENTRADA = """
............
........0...
.....0......
.......0....
....0.......
......A.....
............
............
........A...
.........A..
............
............
"""
class Campo(Labirinto):
def __init__(self, labirinto: list[list[str]]):
super().__init__(labirinto)
self.anti_sinais = {}
self.acha_antenas()
def desenha(self, percurso: dict[str, list[Vector2d]], caractere="X"):
for percurso in percurso.values():
for posição in percurso:
if self.posição_válida(posição):
self[posição.y, posição.x] = caractere
def acha_antenas(self):
self.antenas = {}
for y, linha in enumerate(self.labirinto):
for x, pos in enumerate(linha):
if pos != ".":
self.antenas[pos] = self.antenas.get(pos, []) + [Vector2d(y, x)]
def gera_anti_sinal(self):
self.anti_sinais = {}
for antena, posições in self.antenas.items():
if len(posições) > 1:
for p1, p2 in combinations(posições, 2):
d = p2 - p1
if self.posição_válida(as1 := p1 - d):
self.novo_anti_sinal(antena, as1)
if self.posição_válida(as2 := p2 + d):
self.novo_anti_sinal(antena, as2)
def novo_anti_sinal(self, antena, posição: Vector2d):
self.anti_sinais[antena] = self.anti_sinais.get(antena, []) + [posição]
def gera_anti_sinal_com_resonância(self):
self.anti_sinais = {}
for antena, posições in self.antenas.items():
for posição in self.antenas[antena]:
self.novo_anti_sinal(antena, posição)
if len(posições) > 1:
for p1, p2 in combinations(posições, 2):
d = p2 - p1
while self.posição_válida(as1 := p1 - d):
self.novo_anti_sinal(antena, as1)
p1 = as1
while self.posição_válida(as2 := p2 + d):
self.novo_anti_sinal(antena, as2)
p2 = as2
def conta_anti_sinais(self):
únicos = set()
for a in self.anti_sinais.values():
for anti_sinal in a:
únicos.add(anti_sinal)
return len(únicos)
teste_entrada = string_para_matriz(ENTRADA)
campo = Campo(teste_entrada)
campo.gera_anti_sinal()
resposta_parte_1_teste = campo.conta_anti_sinais()
print("anti-sinais únicos (teste - parte 1):", resposta_parte_1_teste)
assert resposta_parte_1_teste == 14
entrada_parte_1 = string_para_matriz(le_arquivo_sem_linhas_em_branco("08/input.txt"))
campo_parte_1 = Campo(entrada_parte_1)
campo_parte_1.gera_anti_sinal()
resposta_parte_1 = campo_parte_1.conta_anti_sinais()
print("anti-sinais únicos (parte 1):", resposta_parte_1)
assert resposta_parte_1 == 276
campo = Campo(teste_entrada)
campo.gera_anti_sinal_com_resonância()
resposta_parte2_teste = campo.conta_anti_sinais()
print("anti-sinais únicos com resonância (teste - parte 2):", resposta_parte2_teste)
assert resposta_parte2_teste == 34
campo_parte_2 = Campo(entrada_parte_1)
campo_parte_2.gera_anti_sinal_com_resonância()
resposta_parte_2 = campo_parte_2.conta_anti_sinais()
print("anti-sinais únicos com resonância (parte 2):", resposta_parte_2)
assert resposta_parte_2 == 991
# Saída
# anti-sinais únicos (teste - parte 1): 14
# anti-sinais únicos (parte 1): 276
# anti-sinais únicos com resonância (teste - parte 2): 34
# anti-sinais únicos com resonância (parte 2): 991
Módulo auxiliar#
class Vector2d:
def __init__(self, y, x):
self.x = x
self.y = y
def copy(self):
return Vector2d(self.y, self.x)
def __add__(self, other):
return Vector2d(self.y + other.y, self.x + other.x)
def __sub__(self, other):
return Vector2d(self.y - other.y, self.x - other.x)
def __str__(self):
return f"({self.y}, {self.x})"
def __repr__(self):
return f"Vector2d({self.y}, {self.x})"
def __hash__(self):
return hash(f"({self.y}, {self.x})")
def __eq__(self, other):
if isinstance(other, Vector2d):
return self.x == other.x and self.y == other.y
return False
class Labirinto:
def __init__(self, labirinto):
self.max_y = len(labirinto)
self.max_x = len(labirinto[0])
self.orig_labirinto = labirinto
self.labirinto = [list(linha) for linha in labirinto]
def copy(self):
return Labirinto(self.orig_labirinto)
def posição_válida(self, posição: Vector2d) -> bool:
return 0 <= posição.y < self.max_y and 0 <= posição.x < self.max_x
def __str__(self):
return "\n".join(["".join(linha) for linha in self.labirinto])
def __setitem__(self, yx, value):
self.labirinto[yx[0]][yx[1]] = value
def __getitem__(self, yx):
return self.labirinto[yx[0]][yx[1]]
def le_arquivo_sem_linhas_em_branco(nome_arquivo: str) -> str:
return "\n".join(
zz
for linha in le_arquivo(nome_arquivo).splitlines()
if len(zz := linha.strip()) > 0
)