Day 08

# puzzle prompt: https://adventofcode.com/2024/day/8

import sys
import time

sys.path.insert(0, "..")

from collections import defaultdict
from math import gcd

from base.advent import *


class Solution(InputAsLinesSolution):
    _year = 2024
    _day = 8

    _is_debugging = False

    def GetNodes(self, input):
        nodes = defaultdict(set)
        map = {}
        for i, row in enumerate(input):
            for j, c in enumerate(row):
                map[(i, j)] = 0
                if c != ".":
                    nodes[c].add((i, j))
        return nodes, map

    def FindUniqueLocations(self, input):
        def get_distance(a, b):
            return (a[0] - b[0], a[1] - b[1])

        nodes, map = self.GetNodes(input)

        antinodes = set()
        antinodes_antennas = set()

        for _, antennas in nodes.items():
            for antenna_1 in antennas:
                antinodes_antennas.add(antenna_1)
                for antenna_2 in antennas:
                    if antenna_1 == antenna_2:
                        continue
                    distance = get_distance(antenna_1, antenna_2)
                    div = gcd(
                        abs(distance[0]), abs(distance[1])
                    )  # antinodes in line but not multiple of distance between antennas
                    distance = (distance[0] // div, distance[1] // div)

                    try:
                        candidate = (
                            antenna_1[0] + distance[0],
                            antenna_1[1] + distance[1],
                        )
                        map[candidate]  # hack: check out of bounds

                        antinodes.add(candidate)
                        antinodes_antennas.add(candidate)

                        while True:
                            # antinodes in line: i keep adding the distance until out of bounds
                            candidate = (
                                candidate[0] + distance[0],
                                candidate[1] + distance[1],
                            )
                            map[candidate]  # hack: check out of bounds

                            antinodes_antennas.add(candidate)
                    except (
                        KeyError
                    ):  # thrown when out of bounds (when accesing the map)
                        continue

        return antinodes, antinodes_antennas

    def pt1(self, input):
        self.debug(input)

        res, _ = self.FindUniqueLocations(input)

        return len(res)

    def pt2(self, input):
        self.debug(input)

        _, res = self.FindUniqueLocations(input)

        return len(res)

    def part_1(self):
        start_time = time.time()

        res = self.pt1(self.input)

        end_time = time.time()

        self.solve("1", res, (end_time - start_time))

    def part_2(self):
        start_time = time.time()

        res = self.pt2(self.input)

        end_time = time.time()

        self.solve("2", res, (end_time - start_time))


if __name__ == "__main__":
    solution = Solution()

    solution.part_1()

    solution.part_2()