diff --git a/common/grid.py b/common/grid.py index a527b2d..889975f 100644 --- a/common/grid.py +++ b/common/grid.py @@ -1,9 +1,21 @@ from typing import Optional from pydantic.dataclasses import dataclass + +# from dataclasses import dataclass # disabling pydantic may lead to 5x speed from collections import defaultdict DIRS = {"^": (-1, 0), ">": (0, 1), "v": (1, 0), "<": (0, -1)} +DIRS_8 = { + "^": (-1, 0), + ">": (0, 1), + "v": (1, 0), + "<": (0, -1), + "1": (-1, -1), + "7": (-1, 1), + "L": (1, 1), + "J": (1, -1), +} @dataclass @@ -53,6 +65,13 @@ def from_symbol(symbol: str): symbol, ) + @staticmethod + def from_symbol8(symbol: str): + return Direction( + *DIRS_8[symbol], + symbol, + ) + @dataclass class Cursor: diff --git a/requirements/base.txt b/requirements/base.txt index 903705e..ddc5f06 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,2 +1,3 @@ +numpy pydantic requests diff --git a/setup.cfg b/setup.cfg index 941a1b7..7edfc2c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -4,3 +4,4 @@ ignore = E203,W503,W605,E741 [mypy] plugins = pydantic.mypy +ignore_missing_imports = True diff --git a/tests/y_2024/test_2024_day10.py b/tests/y_2024/test_2024_day10.py new file mode 100644 index 0000000..b95f618 --- /dev/null +++ b/tests/y_2024/test_2024_day10.py @@ -0,0 +1,35 @@ +# from __future__ import annotations + +from unittest.mock import mock_open, patch + +from y_2024.day10 import Day + +with patch( + "builtins.open", + mock_open( + read_data="""89010123 +78121874 +87430965 +96549874 +45678903 +32019012 +01329801 +10456732 +""", # noqa: E501 + ), +): + day = Day() + + +def test__preprocess_input(): + assert True + + +def test_calculate_1(): + r = day._calculate_1() + assert r == 36 + + +def test_calculate_2(): + r = day._calculate_2() + assert r == 81 diff --git a/tests/y_2024/test_2024_day11.py b/tests/y_2024/test_2024_day11.py new file mode 100644 index 0000000..6a2b796 --- /dev/null +++ b/tests/y_2024/test_2024_day11.py @@ -0,0 +1,27 @@ +# from __future__ import annotations + +from unittest.mock import mock_open, patch + +from y_2024.day11 import Day + +with patch( + "builtins.open", + mock_open( + read_data="""125 17""", # noqa: E501 + ), +): + day = Day() + + +def test__preprocess_input(): + assert True + + +def test_calculate_1(): + r = day._calculate_1() + assert r == 55312 + + +def test_calculate_2(): + r = day._calculate_2() + assert r == 65601038650482 diff --git a/tests/y_2024/test_2024_day13.py b/tests/y_2024/test_2024_day13.py new file mode 100644 index 0000000..bfbcc8b --- /dev/null +++ b/tests/y_2024/test_2024_day13.py @@ -0,0 +1,42 @@ +# from __future__ import annotations + +from unittest.mock import mock_open, patch + +from y_2024.day13 import Day + +with patch( + "builtins.open", + mock_open( + read_data="""Button A: X+94, Y+34 +Button B: X+22, Y+67 +Prize: X=8400, Y=5400 + +Button A: X+26, Y+66 +Button B: X+67, Y+21 +Prize: X=12748, Y=12176 + +Button A: X+17, Y+86 +Button B: X+84, Y+37 +Prize: X=7870, Y=6450 + +Button A: X+69, Y+23 +Button B: X+27, Y+71 +Prize: X=18641, Y=10279 +""", # noqa: E501 + ), +): + day = Day() + + +def test__preprocess_input(): + assert True + + +def test_calculate_1(): + r = day._calculate_1() + assert r == 480 + + +def test_calculate_2(): + r = day._calculate_2() + assert r == 875318608908 diff --git a/tests/y_2024/test_2024_day9.py b/tests/y_2024/test_2024_day9.py new file mode 100644 index 0000000..ee29026 --- /dev/null +++ b/tests/y_2024/test_2024_day9.py @@ -0,0 +1,27 @@ +# from __future__ import annotations + +from unittest.mock import mock_open, patch + +from y_2024.day9 import Day + +with patch( + "builtins.open", + mock_open( + read_data="""2333133121414131402""", # noqa: E501 + ), +): + day = Day() + + +def test__preprocess_input(): + assert True + + +def test_calculate_1(): + r = day._calculate_1() + assert r == 1928 + + +def test_calculate_2(): + r = day._calculate_2() + assert r == 2858 diff --git a/y_2024/day0.py b/y_2024/day0.py index 5dc41bf..98a9c43 100644 --- a/y_2024/day0.py +++ b/y_2024/day0.py @@ -3,6 +3,7 @@ from pydantic.dataclasses import dataclass from common.aoc import AoCDay +from common.grid import Grid @dataclass @@ -23,13 +24,16 @@ def _preprocess_input(self): print(f"{self._input_data=}") print(f"{len(self._input_data)=}") print(f"{len(self._input_data[0])=}") + self.grid = Grid.from_input(self._input_data) + self.grid.display() # self.__input_data = [Row(i) for i in self._input_data[0]] self.__input_data = [Row(i) for j in self._input_data for i in j] + for x in self.__input_data: + print(f"{x}") def _calculate_1(self): result = 0 for x in self.__input_data: - print(f"{x}") ... return result diff --git a/y_2024/day10.py b/y_2024/day10.py new file mode 100644 index 0000000..143b420 --- /dev/null +++ b/y_2024/day10.py @@ -0,0 +1,100 @@ +from typing import Optional + +from pydantic.dataclasses import dataclass +from collections import deque +from common.aoc import AoCDay +from common.grid import Grid, Cursor, DIRS, Direction + + +@dataclass +class Row: + original: str + processed: Optional[str] = None + + def __post_init__(self) -> None: + self.processed = "" # self.original + + +class Day(AoCDay): + def __init__(self, test=0): + super().__init__(__name__, test) + + def _preprocess_input(self): + self.grid = Grid.from_input(self._input_data) + self.grid.display() + self.__input_data = [Row(i) for j in self._input_data for i in j] + + def _calculate_1(self): + result = 0 + + for i in self.grid.values["0"]: + visited = set() + frontier = deque() + for j in DIRS: + cur = Cursor(i, Direction.from_symbol(j)) + + if ( + self.grid.grid.get(cur.ahead()) + and int(self.grid.grid.get(cur.ahead())) + - int(self.grid.grid.get(i)) + == 1 + ): + frontier.append(cur.ahead()) + + while len(frontier) > 0: + k = frontier.popleft() + + if self.grid.grid.get(k) == "9": + visited.add(k) + continue + + for j in DIRS: + cur = Cursor(k, Direction.from_symbol(j)) + + if ( + self.grid.grid.get(cur.ahead()) + and int(self.grid.grid.get(cur.ahead())) + - int(self.grid.grid.get(k)) + == 1 + ): + frontier.append(cur.ahead()) + + result += len(visited) + + return result + + def _calculate_2(self): + result = 0 + for i in self.grid.values["0"]: + visited = 0 + frontier = deque() + for j in DIRS: + cur = Cursor(i, Direction.from_symbol(j)) + + if ( + self.grid.grid.get(cur.ahead()) + and int(self.grid.grid.get(cur.ahead())) + - int(self.grid.grid.get(i)) + == 1 + ): + frontier.append(cur.ahead()) + + while len(frontier) > 0: + k = frontier.popleft() + + if self.grid.grid.get(k) == "9": + visited += 1 + + for j in DIRS: + cur = Cursor(k, Direction.from_symbol(j)) + if ( + self.grid.grid.get(cur.ahead()) + and int(self.grid.grid.get(cur.ahead())) + - int(self.grid.grid.get(k)) + == 1 + ): + frontier.append(cur.ahead()) + + result += visited + + return result diff --git a/y_2024/day11.py b/y_2024/day11.py new file mode 100644 index 0000000..fd1b59d --- /dev/null +++ b/y_2024/day11.py @@ -0,0 +1,74 @@ +from typing import Optional + +from pydantic.dataclasses import dataclass + +from common.aoc import AoCDay +import functools + + +@dataclass +class Row: + original: str + processed: Optional[list[str]] = None + + def __post_init__(self) -> None: + self.processed = self.original.split(" ") + + +@functools.lru_cache(maxsize=128000000, typed=False) +def inner(i): + res = [] + if i == "0": + res.append("1") + return res + + if len(i) % 2 == 0: + res.append(str(int(i[: len(i) // 2]))) + res.append(str(int(i[len(i) // 2 :]))) + return res + + res.append(str(int(i) * 2024)) + return res + + +@functools.lru_cache(maxsize=128000000, typed=False) +def calc_len(stones, i): + stones = stones.split("#") + if i == 1: + return len(stones) + + r = 0 + for j in stones: + x = inner(j) + + rex = calc_len("#".join(x), i - 1) + + r += rex + + return r + + +class Day(AoCDay): + def __init__(self, test=0): + super().__init__(__name__, test) + + def _preprocess_input(self): + self.__input_data = [Row(i) for j in self._input_data for i in j] + + def _calculate_1(self): + state = [] + for j in self.__input_data: + for x in j.processed: + state.append(x) + + T = 25 + return calc_len("#".join(state), T + 1) + + def _calculate_2(self): + state = [] + for j in self.__input_data: + for x in j.processed: + state.append(x) + + T = 75 + return calc_len("#".join(state), T + 1) diff --git a/y_2024/day12.py b/y_2024/day12.py new file mode 100644 index 0000000..98a9c43 --- /dev/null +++ b/y_2024/day12.py @@ -0,0 +1,42 @@ +from typing import Optional + +from pydantic.dataclasses import dataclass + +from common.aoc import AoCDay +from common.grid import Grid + + +@dataclass +class Row: + original: str + processed: Optional[str] = None + + def __post_init__(self) -> None: + self.processed = "" # self.original + + +class Day(AoCDay): + def __init__(self, test=0): + super().__init__(__name__, test) + + def _preprocess_input(self): + # self.__input_data = [[int(i) for i in chunk] for chunk in self._input_data] + print(f"{self._input_data=}") + print(f"{len(self._input_data)=}") + print(f"{len(self._input_data[0])=}") + self.grid = Grid.from_input(self._input_data) + self.grid.display() + # self.__input_data = [Row(i) for i in self._input_data[0]] + self.__input_data = [Row(i) for j in self._input_data for i in j] + for x in self.__input_data: + print(f"{x}") + + def _calculate_1(self): + result = 0 + for x in self.__input_data: + ... + return result + + def _calculate_2(self): + result = 0 + return result diff --git a/y_2024/day13.py b/y_2024/day13.py new file mode 100644 index 0000000..c9abf94 --- /dev/null +++ b/y_2024/day13.py @@ -0,0 +1,100 @@ +from typing import Optional +import numpy as np +from pydantic.dataclasses import dataclass + +from common.aoc import AoCDay + + +@dataclass +class Row: + original: str + processed: Optional[list[str]] = None + + def __post_init__(self) -> None: + self.processed = self.original.split(": ") + + +@dataclass +class Button: + name: str + x_incr: int + y_incr: int + + +@dataclass +class Prize: + x: int + y: int + + +@dataclass +class Machine: + ba: Button + bb: Button + pr: Prize + + def find_prize(self, cost=0, x=0, y=0, a=0, b=0, pr_mul=0): + M = np.array( + [[self.ba.x_incr, self.bb.x_incr], [self.ba.y_incr, self.bb.y_incr]], + ) + Minv = np.linalg.inv(M) + pr = np.array([self.pr.x + pr_mul, self.pr.y + pr_mul]) + x = np.matmul(Minv, pr) + + a, b = round(x[0]), round(x[1]) + + if ( + a * self.ba.x_incr + b * self.bb.x_incr != self.pr.x + pr_mul + or a * self.ba.y_incr + b * self.bb.y_incr != self.pr.y + pr_mul + ): + return 0 + + return a * 3 + b + + +class Day(AoCDay): + def __init__(self, test=0): + super().__init__(__name__, test) + + def _preprocess_input(self): + self.machines = [] + for j in self._input_data: + local_machine = [] + for c, i in enumerate(j): + r = Row(i) + if c == 0: + a = Button( + r.processed[0].replace("Button ", ""), + int(r.processed[1].split(", ")[0].replace("X", "")), + int(r.processed[1].split(", ")[1].replace("Y", "")), + ) + + if c == 1: + b = Button( + r.processed[0].replace("Button ", ""), + int(r.processed[1].split(", ")[0].replace("X", "")), + int(r.processed[1].split(", ")[1].replace("Y", "")), + ) + if c == 2: + p = Prize( + r.processed[1].split(", ")[0].replace("X=", ""), + r.processed[1].split(", ")[1].replace("Y=", ""), + ) + local_machine = Machine(a, b, p) + self.machines.append(local_machine) + + def _calculate_1(self): + result = 0 + + for c, x in enumerate(self.machines): + result += x.find_prize() + + return result + + def _calculate_2(self): + result = 0 + + for c, x in enumerate(self.machines): + result += x.find_prize(pr_mul=10000000000000) + + return result diff --git a/y_2024/day4.py b/y_2024/day4.py index 9ea7cdf..b996c87 100644 --- a/y_2024/day4.py +++ b/y_2024/day4.py @@ -1,4 +1,5 @@ from common.aoc import AoCDay +from common.grid import Grid, DIRS_8, Cursor, Direction class Day(AoCDay): @@ -6,103 +7,67 @@ def __init__(self, test=0): super().__init__(__name__, test) def _preprocess_input(self): - self.__input_data = [[i for i in chunk] for chunk in self._input_data] - self.grid = {} - for y in self.__input_data: - for c, x in enumerate(y): - for i, k in enumerate(x): - self.grid[(c, i)] = k + self.grid = Grid.from_input(self._input_data) + self.grid.display() def _calculate_1(self): result = 0 - for y in self.__input_data: - for c, x in enumerate(y): - for i, k in enumerate(x): - if k != "X": - continue - left = (c, i - 1) - r = (c, i + 1) - u = (c - 1, i) - d = (c + 1, i) - lu = (c - 1, i - 1) - ru = (c - 1, i + 1) - ld = (c + 1, i - 1) - rd = (c + 1, i + 1) - if self.grid.get(left) == "M": - if self.grid.get((c, i - 2)) == "A": - if self.grid.get((c, i - 3)) == "S": - result += 1 - if self.grid.get(r) == "M": - if self.grid.get((c, i + 2)) == "A": - if self.grid.get((c, i + 3)) == "S": - result += 1 - if self.grid.get(u) == "M": - if self.grid.get((c - 2, i)) == "A": - if self.grid.get((c - 3, i)) == "S": - result += 1 - if self.grid.get(d) == "M": - if self.grid.get((c + 2, i)) == "A": - if self.grid.get((c + 3, i)) == "S": - result += 1 - if self.grid.get(lu) == "M": - if self.grid.get((c - 2, i - 2)) == "A": - if self.grid.get((c - 3, i - 3)) == "S": - result += 1 - if self.grid.get(ru) == "M": - if self.grid.get((c - 2, i + 2)) == "A": - if self.grid.get((c - 3, i + 3)) == "S": - result += 1 - if self.grid.get(ld) == "M": - if self.grid.get((c + 2, i - 2)) == "A": - if self.grid.get((c + 3, i - 3)) == "S": - result += 1 - if self.grid.get(rd) == "M": - if self.grid.get((c + 2, i + 2)) == "A": - if self.grid.get((c + 3, i + 3)) == "S": + for value in self.grid.values: + if value != "X": + continue + for pos in self.grid.values[value]: + for dir in DIRS_8: + cur = Cursor(pos, Direction.from_symbol8(dir)) + if self.grid.grid.get(cur.ahead()) == "M": + cur1 = Cursor(cur.ahead(), Direction.from_symbol8(dir)) + if self.grid.grid.get(cur1.ahead()) == "A": + cur2 = Cursor(cur1.ahead(), Direction.from_symbol8(dir)) + if self.grid.grid.get(cur2.ahead()) == "S": result += 1 + return result def _calculate_2(self): result = 0 - for y in self.__input_data: - for c, x in enumerate(y): - for i, k in enumerate(x): - if k != "A": - continue - lu = (c - 1, i - 1) - ru = (c - 1, i + 1) - ld = (c + 1, i - 1) - rd = (c + 1, i + 1) - - if ( - self.grid.get(lu) == "M" - and self.grid.get(rd) == "S" - and self.grid.get(ru) == "M" - and self.grid.get(ld) == "S" - ): - result += 1 - - if ( - self.grid.get(lu) == "S" - and self.grid.get(rd) == "M" - and self.grid.get(ru) == "S" - and self.grid.get(ld) == "M" - ): - result += 1 - - if ( - self.grid.get(lu) == "M" - and self.grid.get(rd) == "S" - and self.grid.get(ru) == "S" - and self.grid.get(ld) == "M" - ): - result += 1 + dirs_x = {k: v for k, v in DIRS_8.items() if k in ["1", "7", "L", "J"]} + # print(dirs_x) + for value in self.grid.values: + if value != "A": + continue + for pos in self.grid.values[value]: + ones = {} + for dir in dirs_x: + cur = Cursor(pos, Direction.from_symbol8(dir)) + ones[cur.dir.icon] = self.grid.grid.get(cur.ahead()) + # print(ones) + if ( + ones["1"] == "M" + and ones["L"] == "S" + and ones["7"] == "M" + and ones["J"] == "S" + ): + result += 1 + if ( + ones["1"] == "S" + and ones["L"] == "M" + and ones["7"] == "S" + and ones["J"] == "M" + ): + result += 1 - if ( - self.grid.get(lu) == "S" - and self.grid.get(rd) == "M" - and self.grid.get(ru) == "M" - and self.grid.get(ld) == "S" - ): - result += 1 + if ( + ones["1"] == "M" + and ones["L"] == "S" + and ones["7"] == "S" + and ones["J"] == "M" + ): + result += 1 + if ( + ones["1"] == "S" + and ones["L"] == "M" + and ones["7"] == "M" + and ones["J"] == "S" + ): + result += 1 + # result = 0 return result diff --git a/y_2024/day9.py b/y_2024/day9.py new file mode 100644 index 0000000..df36f2e --- /dev/null +++ b/y_2024/day9.py @@ -0,0 +1,123 @@ +from typing import Optional + +from pydantic.dataclasses import dataclass +from collections import deque +from common.aoc import AoCDay + + +@dataclass +class Row: + original: str + processed: Optional[str] = None + + def __post_init__(self) -> None: + self.processed = "" # self.original + + +@dataclass +class Elem: + x: str + + +class Day(AoCDay): + def __init__(self, test=0): + super().__init__(__name__, test) + + def _preprocess_input(self): + # self.__input_data = [[int(i) for i in chunk] for chunk in self._input_data] + print(f"{self._input_data=}") + # print(f"{len(self._input_data)=}") + # print(f"{len(self._input_data[0])=}") + # self.grid = Grid.from_input(self._input_data) + # self.grid.display() + # self.__input_data = [Row(i) for i in self._input_data[0]] + self.__input_data = [Row(i) for j in self._input_data for i in j] + + def _calculate_1(self): + result = 0 + r = [] + for c, x in enumerate(self.__input_data[0].original): + m = c // 2 if c % 2 == 0 else "." + + for i in range(int(x)): + r.append(Elem(str(m))) + + c = 0 + + for i in range(len(r) - 1): + x = r[len(r) - 1 - i].x + try: + while r[c].x != ".": + c += 1 + r[c].x = x + r[len(r) - 1 - i] = Elem("#") + except Exception: + break + result = 0 + for c, i in enumerate(r): + if i.x != "#": + result += c * int(i.x) + + return result + + def _calculate_2(self): + result = 0 + r = [] + for c, x in enumerate(self.__input_data[0].original): + m = c // 2 if c % 2 == 0 else "." + + for i in range(int(x)): + r.append(Elem(str(m))) + + chunks = [] + chunks2 = [] + c = [] + c2 = [] + x = r[-1] + c.append(x) + c2.append(len(r) - 1) + for i in range(1, len(r)): + if r[-1 - i].x == x.x: + c.append(r[len(r) - 1 - i]) + c2.append(len(r) - 1 - i) + else: + if c[0].x != ".": + chunks.append(c) + chunks2.append(c2[::-1]) + c = [] + c2 = [] + x = r[-1 - i] + c.append(x) + c2.append(len(r) - 1 - i) + + slots = [] + slo = deque() + for i in range(len(r)): + if r[i].x == ".": + slo.append(i) + else: + if len(slo) > 0: + slots.append(slo) + slo = deque() + + c = 0 + + for c, i in enumerate(chunks): + for j in range(len(slots)): + if len(i) <= len(slots[j]): + if slots[j][0] > chunks2[c][0]: + break + + for m in chunks2[c]: + r[m] = Elem("#") + n = slots[j].popleft() + + r[n] = i[0] + break + + result = 0 + for c, i in enumerate(r): + if i.x not in ["#", "."]: + result += c * int(i.x) + + return result