summaryrefslogtreecommitdiffstats
path: root/day3/__init__.py
blob: aca90da910016d752a94131056ee316134dcb73a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# -*- coding: utf-8 -*-
from abc import ABC
from functools import reduce
from typing import Optional, Iterator

from aoc import BaseAssignment, I, T
from aoc.datastructures import Coordinate


class Assignment(BaseAssignment, ABC):
    def symbol_overlap(
        self, number_coordinates: list[Coordinate], symbols: set[Coordinate]
    ) -> set[Coordinate]:
        neighbours = {
            neighbour
            for coordinate in number_coordinates
            for neighbour in coordinate.neighbours()
        }

        return neighbours & symbols

    def read_input(
        self, example=False
    ) -> tuple[list[str], list[tuple[list[Coordinate], int]], set[Coordinate]]:
        schematic = list(super().read_input(example))
        numbers: list[tuple[list[Coordinate], int]] = list()
        symbols: set[Coordinate] = set()

        for y, row in enumerate(schematic):
            number: Optional[list[list[Coordinate], str]] = None

            for x, item in enumerate(row):
                if item.isdigit():
                    if number is None:
                        number = [[], ""]

                    number[0].append(Coordinate(x, y))
                    number[1] += item
                    continue

                if number is not None:
                    numbers.append((number[0], int(number[1])))
                    number = None

                if item != ".":
                    symbols.add(Coordinate(x, y))

            if number is not None:
                numbers.append((number[0], int(number[1])))

        return schematic, numbers, symbols


class AssignmentOne(Assignment):
    example_result = 4361

    def is_part_number(
        self, number_coordinates: list[Coordinate], symbols: set[Coordinate]
    ) -> bool:
        return len(self.symbol_overlap(number_coordinates, symbols)) > 0

    def run(
        self, input: tuple[list[tuple[list[Coordinate], int]], set[Coordinate]]
    ) -> int:
        _, numbers, symbols = input

        part_numbers = []

        for coordinates, number in numbers:
            if self.is_part_number(coordinates, symbols):
                part_numbers.append(number)

        return sum(part_numbers)


class AssignmentTwo(Assignment):
    example_result = 467835

    def run(
        self, input: tuple[list[tuple[list[Coordinate], int]], set[Coordinate]]
    ) -> int:
        schematic, numbers, symbols = input

        possible_gears = {}

        for coordinates, number in numbers:
            for symbol in self.symbol_overlap(coordinates, symbols):
                if schematic[symbol.y][symbol.x] != "*":
                    continue

                if symbol not in possible_gears:
                    possible_gears[symbol] = []

                possible_gears[symbol].append(number)

        return sum(
            [
                reduce(lambda total, number: total * number, numbers, 1)
                for symbol, numbers in possible_gears.items()
                if len(numbers) > 1
            ]
        )