From 1562f97efcaabf54e4044b57abbd5841a7af0326 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sat, 24 Dec 2022 14:53:14 +0100 Subject: [PATCH 1/4] 2022: d24: ex1: add input --- 2022/d24/ex1/input | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 2022/d24/ex1/input diff --git a/2022/d24/ex1/input b/2022/d24/ex1/input new file mode 100644 index 0000000..f64ff83 --- /dev/null +++ b/2022/d24/ex1/input @@ -0,0 +1,22 @@ +#.###################################################################################################################################################### +#^>^^<><^>^^v>vv^v^>>^<>^v^>>v>v><^..>v>>>>^>>.v>.v><<.^>>^^^>v>>^.>vv<<.<^v>^v<>^^.^.<<>^<^>.>vvv><^<^>v.^<># +#<..v<<^^.^^v<<>>v>>.<>>^vv^^v>v^>>>v^vv<<><>>v.>.v>.<..v>^^>^<><>vv>>v>.><.<v^vv<>vvvvv^<^v>.# +#<>>^v^v>v>.^v<>vvv>>^<<^>>v^^^.<<<.>>v.>.v^.>>>^><>v>>v^<.v^<.vv^>^v<><>>v^^<^>..<<^<<<^<^>^<>v>vvv>>^<<>><><<^^.<v># +#>.<<<^..>><>v>^vv>vv^^>>.><^>><^.^<>^v>.<^<>>>^>>^><^><^.^>.><><><^>^>>^>>>^<.>>^<^>vv<^v^>.>^>>vv.^<>>^>>>><>.^.>v<.^<>v^>v>^^>>v<><># +#>>v<^v^<^^><>><>.>v^v>^v.>v^.>>.><><<>^><<>^>^v>v^^^>>^v<<<>vv>>v<^^^<<<<>v<^>>.^v>><.v^<.>>vv>v><><.^^^v><.<^>>><# +#>>vv<^vv<<>^v<^v.>>v<><>.>^.^....<<^<.>^^^<><>.>^<>>^^><>>v^vv^<>v^^^^v.<><^><^v<<.v>>><>>><.v>v>>^.^>^># +#>^v>v^vv^.^<<<^><>v^<.>>^^^<^<^>.>>^<<.^>><<.>^^.vv>.vv><^^.<<v..<^<.vvv<..v>.>>^v<^^<^v^>^.<<^^.^^<>v<<<v>><># +#<^>>^>><<>v.v>.vvv><^>v..<<<>>><^v><^>..<^^v>.<<>.v..^<.^vv.>^><>vv><<>v^>v>><^^>^>><^>.>^^<<><><^.>^v<>v^.>v><>>.v^..v># +#<.<<<^><><^<<^>><..>^<<<>^.<>>v..v^^<<<.><.^.<^>^vvv^^^^>v>^>>>>v<><>v.<>>^.<>v^<.^vv>v^.><<^vv<>><>v><^v>>^^^.v^v>v>^v^>.v<<^>.^v^>vv<# +#<<>^^.^>v.<>>^>^.<<^>^><<>v^.><<<.^^^<<^><<^^>.>vv>^>^^v<.^>.^>^<^>^.<>^v.^^^.v>^.^v^<<<><<>v.>>>>^..v>^^vv^v>># +#<^.<^^><>^^^<<>>^>.^^^<^<^<^^<^v.^^v>^v<>.^v^^<<..v<^<>>^<..<^.>>^>.^v.>>v^.v..v.<^vvv^v<><><<^v^v.^^<<>^vv>^^>v>^v<<^>^<^.^# +#<^^v^v>^<^><>v><.>^>^..v^>^v^>^><>v<^v^<v>^vv<^v.<><>vv>>>>v<>^vv<^v<>^^.v>>^^<<.<^>^>>vv>vv^>^v<<>.>^>^v># +#<<>>>.<<>^>>v>^<^v>><<^v<><<.^<^<>>><^v>>v>^<^vv.<^^^.vv^<<<>^^>v>^v.>^^v>v.<>vvv><v>^v.^>vv<>^>.>.v^v.v^vv^<.<>.>vv>^>v^<.<# +#>^<^<<.<<..>>.>^^v>>v>^^>^^>>>v>v^<>v>>^>vvv>><>>vv>.>^.^<>.>^.v<>^^<^v^>>.>v.v<><<<>^<<^>^>^>^.vv..<>v<^># +#..v<<^v^^>^>^<^v^v^^v>><^><<^v.<.vv^v<<>^v<<^v^v^^.>^>v<^^v...>>vv.<^v>v^<<^v^>v<^>v^<>.>v^^^^.^<>^^.^><.<.>>.><.# +#v^v<<^v^>>^vv.v>>v>.vv^>>vv.>v>>>^.>>><.^.^<><>^vv<^<>><^v^>>.<>v<>v<....^^><.<^^>v^v>v<<>>^<>.v.<.vv<.>^.^v^>.># +#<v>>^>.>^^^<.>>^<><>>.>^v^<.v^^.^<<^>^.^v^^v<>>.>.<>.^.v>.^><^>v>v<>.^v<><.<<.><<^v<><><^>v^...v<<^^<^>v>^^vv^^vvv>^>v>>.>>v^^.vv>^# +#>v<>^v><>^>>><<^.^v<v<>>>.^v><^v<<>v<^^.<.><>^<><.v>><^>^>>.<>vvvv.^^v<<>^<>>><>>^v.<^>.^>><<>v<.<^<^>v^^>v^.><# +#>vv^<^.<^>v^v^<><>v<>>v^<>^>>>^v^>v<<^.^>v^v^<^<^^>^^^vv^.<><>><^v.<^<.<>.>^<^<^<>v<^.^vv><.v<^>># +#>v^.v>^v<>>v^>>>^^^^>.v<<^>^v>.^^>^>v^<^v^^<^^v>v>.v^^v><^^.><^^>>v>v^v><>vv><>^vvv>>v>>^v^^<^>>v^<^vv^<^^^^vv<>^^>v^v<><<<<# +######################################################################################################################################################.# From 4211451deb03e320db95d170fc0b132f8c414e88 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sat, 24 Dec 2022 14:53:27 +0100 Subject: [PATCH 2/4] 2022: d24: ex1: add solution --- 2022/d24/ex1/ex1.py | 147 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100755 2022/d24/ex1/ex1.py diff --git a/2022/d24/ex1/ex1.py b/2022/d24/ex1/ex1.py new file mode 100755 index 0000000..079de0e --- /dev/null +++ b/2022/d24/ex1/ex1.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python + +import dataclasses +import enum +import sys +from collections import defaultdict, deque +from collections.abc import Iterator +from typing import NamedTuple + + +class Point(NamedTuple): + x: int + y: int + + def __add__(self, other): + if not isinstance(other, Point): + return NotImplemented + return Point(self.x + other.x, self.y + other.y) + + def __sub__(self, other): + if not isinstance(other, Point): + return NotImplemented + return Point(self.x - other.x, self.y - other.y) + + +class Direction(str, enum.Enum): + UP = "^" + DOWN = "v" + LEFT = "<" + RIGHT = ">" + + def to_delta(self) -> Point: + match self: + case Direction.UP: + return Point(-1, 0) + case Direction.DOWN: + return Point(1, 0) + case Direction.LEFT: + return Point(0, -1) + case Direction.RIGHT: + return Point(0, 1) + + +@dataclasses.dataclass +class ValleyMap: + start: Point + goal: Point + valley_corners: tuple[Point, Point] + tornadoes: dict[Point, Direction] + + @classmethod + def from_input(cls, input: list[str]) -> "ValleyMap": + tornadoes: dict[Point, Direction] = {} + for x, line in enumerate(input, start=1): + for y, c in enumerate(line, start=1): + if c in ("#", "."): + continue + tornadoes[Point(x, y)] = Direction(c) + return cls( + # Start position is always above the upper left corner of valley + start=Point(1, 2), + # Goal position is always under the lower left corner of valley + goal=Point(len(input), len(input[0]) - 1), + # Valley is surrounded by walls, except entrance and exit + valley_corners=(Point(2, 2), Point(len(input) - 1, len(input[0]) - 1)), + tornadoes=tornadoes, + ) + + def _is_in_valley(self, p: Point) -> bool: + # Valley also includes start/end + if p in (self.start, self.goal): + return True + # Otherwise, just do a bounds check for inside the walls + ((minx, miny), (maxx, maxy)) = self.valley_corners + return (minx <= p.x <= maxx) and (miny <= p.y <= maxy) + + def _wrap_tornado(self, p: Point) -> Point: + if self._is_in_valley(p): + return p + x, y = p + h = self.valley_corners[1].x - self.valley_corners[0].x + 1 + w = self.valley_corners[1].y - self.valley_corners[0].y + 1 + if x == 1: + x += h + if y == 1: + y += w + if x > self.valley_corners[1].x: + x -= h + if y > self.valley_corners[1].y: + y -= w + return Point(x, y) + + def navigate(self) -> int: + TornadoesMap = dict[Point, list[Direction]] + + def move_tornadoes(map: TornadoesMap) -> dict[Point, list[Direction]]: + res: dict[Point, list[Direction]] = defaultdict(list) + for p, tornadoes in map.items(): + for t in tornadoes: + new_pos = self._wrap_tornado(p + t.to_delta()) + res[new_pos].append(t) + return dict(res) + + def moves(p: Point) -> Iterator[Point]: + yield p + for dx, dy in ((-1, 0), (1, 0), (0, -1), (0, 1)): + yield p + Point(dx, dy) + + # Do a BFS to find the fastest route + queue: deque[tuple[int, Point]] = deque([(0, self.start)]) + seen: set[tuple[int, Point]] = set() + tornado_history = [{p: [t] for p, t in self.tornadoes.items()}] + while queue: + dist, pos = queue.popleft() + # If goal found, return total distance + if pos == self.goal: + return dist + # Check that we don't do redundant work + if (dist, pos) in seen: + continue + seen.add((dist, pos)) + if len(tornado_history) <= (dist + 1): + tornado_history.append(move_tornadoes(tornado_history[-1])) + for new_pos in moves(pos): + # Can't move into the walls, but can move in start/end + if not self._is_in_valley(new_pos): + continue + # Can't occupy same space as tornadoes + if new_pos in tornado_history[dist + 1]: + continue + # Enqueue this move to the search space + queue.append((dist + 1, new_pos)) + assert False # Sanity check + + +def solve(input: list[str]) -> int: + valley = ValleyMap.from_input(input) + return valley.navigate() + + +def main() -> None: + input = sys.stdin.read().splitlines() + print(solve(input)) + + +if __name__ == "__main__": + main() From 7759412af3aaa0fa7d458c9b3e123fa3eeaab647 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sat, 24 Dec 2022 14:54:21 +0100 Subject: [PATCH 3/4] 2022: d24: ex2: add input --- 2022/d24/ex2/input | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 2022/d24/ex2/input diff --git a/2022/d24/ex2/input b/2022/d24/ex2/input new file mode 100644 index 0000000..f64ff83 --- /dev/null +++ b/2022/d24/ex2/input @@ -0,0 +1,22 @@ +#.###################################################################################################################################################### +#^>^^<><^>^^v>vv^v^>>^<>^v^>>v>v><^..>v>>>>^>>.v>.v><<.^>>^^^>v>>^.>vv<<.<^v>^v<>^^.^.<<>^<^>.>vvv><^<^>v.^<># +#<..v<<^^.^^v<<>>v>>.<>>^vv^^v>v^>>>v^vv<<><>>v.>.v>.<..v>^^>^<><>vv>>v>.><.<v^vv<>vvvvv^<^v>.# +#<>>^v^v>v>.^v<>vvv>>^<<^>>v^^^.<<<.>>v.>.v^.>>>^><>v>>v^<.v^<.vv^>^v<><>>v^^<^>..<<^<<<^<^>^<>v>vvv>>^<<>><><<^^.<v># +#>.<<<^..>><>v>^vv>vv^^>>.><^>><^.^<>^v>.<^<>>>^>>^><^><^.^>.><><><^>^>>^>>>^<.>>^<^>vv<^v^>.>^>>vv.^<>>^>>>><>.^.>v<.^<>v^>v>^^>>v<><># +#>>v<^v^<^^><>><>.>v^v>^v.>v^.>>.><><<>^><<>^>^v>v^^^>>^v<<<>vv>>v<^^^<<<<>v<^>>.^v>><.v^<.>>vv>v><><.^^^v><.<^>>><# +#>>vv<^vv<<>^v<^v.>>v<><>.>^.^....<<^<.>^^^<><>.>^<>>^^><>>v^vv^<>v^^^^v.<><^><^v<<.v>>><>>><.v>v>>^.^>^># +#>^v>v^vv^.^<<<^><>v^<.>>^^^<^<^>.>>^<<.^>><<.>^^.vv>.vv><^^.<<v..<^<.vvv<..v>.>>^v<^^<^v^>^.<<^^.^^<>v<<<v>><># +#<^>>^>><<>v.v>.vvv><^>v..<<<>>><^v><^>..<^^v>.<<>.v..^<.^vv.>^><>vv><<>v^>v>><^^>^>><^>.>^^<<><><^.>^v<>v^.>v><>>.v^..v># +#<.<<<^><><^<<^>><..>^<<<>^.<>>v..v^^<<<.><.^.<^>^vvv^^^^>v>^>>>>v<><>v.<>>^.<>v^<.^vv>v^.><<^vv<>><>v><^v>>^^^.v^v>v>^v^>.v<<^>.^v^>vv<# +#<<>^^.^>v.<>>^>^.<<^>^><<>v^.><<<.^^^<<^><<^^>.>vv>^>^^v<.^>.^>^<^>^.<>^v.^^^.v>^.^v^<<<><<>v.>>>>^..v>^^vv^v>># +#<^.<^^><>^^^<<>>^>.^^^<^<^<^^<^v.^^v>^v<>.^v^^<<..v<^<>>^<..<^.>>^>.^v.>>v^.v..v.<^vvv^v<><><<^v^v.^^<<>^vv>^^>v>^v<<^>^<^.^# +#<^^v^v>^<^><>v><.>^>^..v^>^v^>^><>v<^v^<v>^vv<^v.<><>vv>>>>v<>^vv<^v<>^^.v>>^^<<.<^>^>>vv>vv^>^v<<>.>^>^v># +#<<>>>.<<>^>>v>^<^v>><<^v<><<.^<^<>>><^v>>v>^<^vv.<^^^.vv^<<<>^^>v>^v.>^^v>v.<>vvv><v>^v.^>vv<>^>.>.v^v.v^vv^<.<>.>vv>^>v^<.<# +#>^<^<<.<<..>>.>^^v>>v>^^>^^>>>v>v^<>v>>^>vvv>><>>vv>.>^.^<>.>^.v<>^^<^v^>>.>v.v<><<<>^<<^>^>^>^.vv..<>v<^># +#..v<<^v^^>^>^<^v^v^^v>><^><<^v.<.vv^v<<>^v<<^v^v^^.>^>v<^^v...>>vv.<^v>v^<<^v^>v<^>v^<>.>v^^^^.^<>^^.^><.<.>>.><.# +#v^v<<^v^>>^vv.v>>v>.vv^>>vv.>v>>>^.>>><.^.^<><>^vv<^<>><^v^>>.<>v<>v<....^^><.<^^>v^v>v<<>>^<>.v.<.vv<.>^.^v^>.># +#<v>>^>.>^^^<.>>^<><>>.>^v^<.v^^.^<<^>^.^v^^v<>>.>.<>.^.v>.^><^>v>v<>.^v<><.<<.><<^v<><><^>v^...v<<^^<^>v>^^vv^^vvv>^>v>>.>>v^^.vv>^# +#>v<>^v><>^>>><<^.^v<v<>>>.^v><^v<<>v<^^.<.><>^<><.v>><^>^>>.<>vvvv.^^v<<>^<>>><>>^v.<^>.^>><<>v<.<^<^>v^^>v^.><# +#>vv^<^.<^>v^v^<><>v<>>v^<>^>>>^v^>v<<^.^>v^v^<^<^^>^^^vv^.<><>><^v.<^<.<>.>^<^<^<>v<^.^vv><.v<^>># +#>v^.v>^v<>>v^>>>^^^^>.v<<^>^v>.^^>^>v^<^v^^<^^v>v>.v^^v><^^.><^^>>v>v^v><>vv><>^vvv>>v>>^v^^<^>>v^<^vv^<^^^^vv<>^^>v^v<><<<<# +######################################################################################################################################################.# From d1194e8c5fccd7d7f55130d36c61abc131c6bb94 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sat, 24 Dec 2022 14:54:26 +0100 Subject: [PATCH 4/4] 2022: d24: ex2: add solution --- 2022/d24/ex2/ex2.py | 164 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100755 2022/d24/ex2/ex2.py diff --git a/2022/d24/ex2/ex2.py b/2022/d24/ex2/ex2.py new file mode 100755 index 0000000..46b4dc0 --- /dev/null +++ b/2022/d24/ex2/ex2.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python + +import dataclasses +import enum +import sys +from collections import defaultdict, deque +from collections.abc import Iterator +from typing import NamedTuple + + +class Point(NamedTuple): + x: int + y: int + + def __add__(self, other): + if not isinstance(other, Point): + return NotImplemented + return Point(self.x + other.x, self.y + other.y) + + def __sub__(self, other): + if not isinstance(other, Point): + return NotImplemented + return Point(self.x - other.x, self.y - other.y) + + +class Direction(str, enum.Enum): + UP = "^" + DOWN = "v" + LEFT = "<" + RIGHT = ">" + + def to_delta(self) -> Point: + match self: + case Direction.UP: + return Point(-1, 0) + case Direction.DOWN: + return Point(1, 0) + case Direction.LEFT: + return Point(0, -1) + case Direction.RIGHT: + return Point(0, 1) + + +@dataclasses.dataclass +class ValleyMap: + start: Point + goal: Point + valley_corners: tuple[Point, Point] + tornadoes: dict[Point, Direction] + + @classmethod + def from_input(cls, input: list[str]) -> "ValleyMap": + tornadoes: dict[Point, Direction] = {} + for x, line in enumerate(input, start=1): + for y, c in enumerate(line, start=1): + if c in ("#", "."): + continue + tornadoes[Point(x, y)] = Direction(c) + return cls( + # Start position is always above the upper left corner of valley + start=Point(1, 2), + # Goal position is always under the lower left corner of valley + goal=Point(len(input), len(input[0]) - 1), + # Valley is surrounded by walls, except entrance and exit + valley_corners=(Point(2, 2), Point(len(input) - 1, len(input[0]) - 1)), + tornadoes=tornadoes, + ) + + def _is_in_valley(self, p: Point) -> bool: + # Valley also includes start/end + if p in (self.start, self.goal): + return True + # Otherwise, just do a bounds check for inside the walls + ((minx, miny), (maxx, maxy)) = self.valley_corners + return (minx <= p.x <= maxx) and (miny <= p.y <= maxy) + + def _wrap_tornado(self, p: Point) -> Point: + if self._is_in_valley(p): + return p + x, y = p + h = self.valley_corners[1].x - self.valley_corners[0].x + 1 + w = self.valley_corners[1].y - self.valley_corners[0].y + 1 + if x == 1: + x += h + if y == 1: + y += w + if x > self.valley_corners[1].x: + x -= h + if y > self.valley_corners[1].y: + y -= w + return Point(x, y) + + def navigate(self) -> int: + TornadoesMap = dict[Point, list[Direction]] + + def move_tornadoes(map: TornadoesMap) -> dict[Point, list[Direction]]: + res: dict[Point, list[Direction]] = defaultdict(list) + for p, tornadoes in map.items(): + for t in tornadoes: + new_pos = self._wrap_tornado(p + t.to_delta()) + res[new_pos].append(t) + return dict(res) + + def moves(p: Point) -> Iterator[Point]: + yield p + for dx, dy in ((-1, 0), (1, 0), (0, -1), (0, 1)): + yield p + Point(dx, dy) + + def bfs( + start: Point, goal: Point, tornadoes: TornadoesMap + ) -> tuple[int, TornadoesMap]: + # Do a BFS to find the fastest route + queue: deque[tuple[int, Point]] = deque([(0, start)]) + seen: set[tuple[int, Point]] = set() + tornado_history = [tornadoes] + while queue: + dist, pos = queue.popleft() + # If goal found, return total distance + if pos == goal: + return dist, tornado_history[dist] + # Check that we don't do redundant work + if (dist, pos) in seen: + continue + seen.add((dist, pos)) + if len(tornado_history) <= (dist + 1): + tornado_history.append(move_tornadoes(tornado_history[-1])) + for new_pos in moves(pos): + # Can't move into the walls, but can move in start/end + if not self._is_in_valley(new_pos): + continue + # Can't occupy same space as tornadoes + if new_pos in tornado_history[dist + 1]: + continue + # Enqueue this move to the search space + queue.append((dist + 1, new_pos)) + assert False # Sanity check + + tornadoes = {p: [t] for p, t in self.tornadoes.items()} + total = 0 + for start, end in ( + # First travel + (self.start, self.goal), + # Back for snacks + (self.goal, self.start), + # Second travel + (self.start, self.goal), + ): + dist, tornadoes = bfs(start, end, tornadoes) + total += dist + return total + + +def solve(input: list[str]) -> int: + valley = ValleyMap.from_input(input) + return valley.navigate() + + +def main() -> None: + input = sys.stdin.read().splitlines() + print(solve(input)) + + +if __name__ == "__main__": + main()