Skip to content
This repository has been archived by the owner on Dec 25, 2024. It is now read-only.

Commit

Permalink
day 15
Browse files Browse the repository at this point in the history
  • Loading branch information
asottile committed Dec 16, 2024
1 parent 79ca799 commit e98d271
Show file tree
Hide file tree
Showing 6 changed files with 328 additions and 7 deletions.
Empty file added day15/__init__.py
Empty file.
9 changes: 9 additions & 0 deletions day15/i2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#######
#...#.#
#.....#
#.@OO.#
#..O..#
#.....#
#######

>>>>>
71 changes: 71 additions & 0 deletions day15/input.txt

Large diffs are not rendered by default.

87 changes: 87 additions & 0 deletions day15/part1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
from __future__ import annotations

import argparse
import os.path

import pytest

import support

INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt')


def compute(s: str) -> int:
map_s, moves_s = s.split('\n\n')
walls = support.parse_coords_hash(map_s)
boxes = support.parse_coords_hash(map_s, wall='O')
bot, = support.parse_coords_hash(map_s, wall='@')

moves_s = ''.join(moves_s.split())
for c in moves_s:
d = support.Direction4.from_c(c)

boxes_to_move = set()
pos = d.apply(*bot)
while pos in boxes:
boxes_to_move.add(pos)
pos = d.apply(*pos)

if pos in walls:
continue

bot = d.apply(*bot)
boxes -= boxes_to_move
boxes |= {d.apply(*box) for box in boxes_to_move}

return sum(100 * y + x for x, y in boxes)


INPUT_S = '''\
##########
#..O..O.O#
#......O.#
#.OO..O.O#
#[email protected].#
#O#..O...#
#O..O..O.#
#.OO.O.OO#
#....O...#
##########
<vv>^<v^>v>^vv^v>v<>v^v<v<^vv<<<^><<><>>v<vvv<>^v^>^<<<><<v<<<v^vv^v>^
vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<<v<^v>^<^^>>>^<v<v
><>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^<v>v^^<^^vv<
<<v<^>>^^^^>>>v^<>vvv^><v<<<>^^^vv^<vvv>^>v<^^^^v<>^>vvvv><>>v^<<^^^^^
^><^><>>><>^^<<^^v>>><^<v>^<vv>>v>>>^v><>^v><<<<v>>v<v<v>vvv>^<><<>^><
^>><>^v<><^vvv<^^<><v<<<<<><^v<<<><<<^^<v<^^^><^>>^<v^><<<^>>^v<v^v<v^
>^>>^v>vv>^<<^v<>><<><<v<<v><>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^
<><^^>^^^<><vvvvv^v<v<<>^v<v>v<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<>
^^>vv<^v^v<vv>^<><v<^v>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<><<v>
v^^>>><<^^<>>^v^<v^vv<>v^<<>^<^v^v><^<<<><<^<v><v<>vv>>v><v^<vv<>v^<<^
'''
EXPECTED = 10092


@pytest.mark.parametrize(
('input_s', 'expected'),
(
(INPUT_S, EXPECTED),
),
)
def test(input_s: str, expected: int) -> None:
assert compute(input_s) == expected


def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument('data_file', nargs='?', default=INPUT_TXT)
args = parser.parse_args()

with open(args.data_file) as f, support.timing():
print(compute(f.read()))

return 0


if __name__ == '__main__':
raise SystemExit(main())
142 changes: 142 additions & 0 deletions day15/part2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
from __future__ import annotations

import argparse
import os.path

import pytest

import support

INPUT_TXT = os.path.join(os.path.dirname(__file__), 'input.txt')


def compute(s: str) -> int:
map_s, moves_s = s.split('\n\n')
map_s = map_s.translate({
ord('#'): '##',
ord('.'): '..',
ord('O'): '[]',
ord('@'): '@.',
})
walls = support.parse_coords_hash(map_s)
boxes = support.parse_coords_hash(map_s, wall='[')
bot, = support.parse_coords_hash(map_s, wall='@')

def _boxes_to_move(d: support.Direction4) -> set[tuple[int, int]]:
boxes_to_move: set[tuple[int, int]] = set()
if d is support.Direction4.LEFT:
pos = d.apply(*bot)
if pos in walls:
return boxes_to_move
pos = d.apply(*pos)
while pos in boxes:
boxes_to_move.add(pos)
pos = d.apply(*pos)
if pos in walls:
return boxes_to_move
pos = d.apply(*pos)
return boxes_to_move
elif d is support.Direction4.RIGHT:
pos = d.apply(*bot)
while pos in boxes:
boxes_to_move.add(pos)
pos = d.apply(*d.apply(*pos))
return boxes_to_move
elif d is support.Direction4.UP or d is support.Direction4.DOWN:
pos1 = d.apply(*bot)
pos2 = support.Direction4.LEFT.apply(*pos1)
layer = {pos1, pos2} & boxes
while layer:
boxes_to_move.update(layer)
newlayer: set[tuple[int, int]] = set()
for box in layer:
pos1 = d.apply(*box)
pos2 = support.Direction4.LEFT.apply(*pos1)
pos3 = support.Direction4.RIGHT.apply(*pos1)
newlayer.update((pos1, pos2, pos3))
layer = newlayer & boxes
return boxes_to_move
else:
raise AssertionError('unreachable')

def _is_box_blocked(box: tuple[int, int], d: support.Direction4) -> bool:
if d is support.Direction4.LEFT:
return d.apply(*box) in walls
elif d is support.Direction4.RIGHT:
return d.apply(*d.apply(*box)) in walls
elif d is support.Direction4.UP or d is support.Direction4.DOWN:
return (
d.apply(*box) in walls or
support.Direction4.RIGHT.apply(*d.apply(*box)) in walls
)
else:
raise AssertionError('unreachable')

moves_s = ''.join(moves_s.split())
for c in moves_s:
d = support.Direction4.from_c(c)

if d.apply(*bot) in walls:
continue

boxes_to_move = _boxes_to_move(d)

if any(_is_box_blocked(box, d) for box in boxes_to_move):
continue

bot = d.apply(*bot)
boxes -= boxes_to_move
boxes |= {d.apply(*box) for box in boxes_to_move}

return sum(100 * y + x for x, y in boxes)


INPUT_S = '''\
##########
#..O..O.O#
#......O.#
#.OO..O.O#
#[email protected].#
#O#..O...#
#O..O..O.#
#.OO.O.OO#
#....O...#
##########
<vv>^<v^>v>^vv^v>v<>v^v<v<^vv<<<^><<><>>v<vvv<>^v^>^<<<><<v<<<v^vv^v>^
vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<<v<^v>^<^^>>>^<v<v
><>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^<v>v^^<^^vv<
<<v<^>>^^^^>>>v^<>vvv^><v<<<>^^^vv^<vvv>^>v<^^^^v<>^>vvvv><>>v^<<^^^^^
^><^><>>><>^^<<^^v>>><^<v>^<vv>>v>>>^v><>^v><<<<v>>v<v<v>vvv>^<><<>^><
^>><>^v<><^vvv<^^<><v<<<<<><^v<<<><<<^^<v<^^^><^>>^<v^><<<^>>^v<v^v<v^
>^>>^v>vv>^<<^v<>><<><<v<<v><>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^
<><^^>^^^<><vvvvv^v<v<<>^v<v>v<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<>
^^>vv<^v^v<vv>^<><v<^v>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<><<v>
v^^>>><<^^<>>^v^<v^vv<>v^<<>^<^v^v><^<<<><<^<v><v<>vv>>v><v^<vv<>v^<<^
'''
EXPECTED = 9021


@pytest.mark.parametrize(
('input_s', 'expected'),
(
(INPUT_S, EXPECTED),
),
)
def test(input_s: str, expected: int) -> None:
assert compute(input_s) == expected


def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument('data_file', nargs='?', default=INPUT_TXT)
args = parser.parse_args()

with open(args.data_file) as f, support.timing():
print(compute(f.read()))

return 0


if __name__ == '__main__':
raise SystemExit(main())
26 changes: 19 additions & 7 deletions support-src/support.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,13 +183,13 @@ def parse_coords_int(s: str) -> dict[tuple[int, int], int]:
return coords


def parse_coords_hash(s: str) -> set[tuple[int, int]]:
coords = set()
for y, line in enumerate(s.splitlines()):
for x, c in enumerate(line):
if c == '#':
coords.add((x, y))
return coords
def parse_coords_hash(s: str, *, wall: str = '#') -> set[tuple[int, int]]:
return {
(x, y)
for y, line in enumerate(s.splitlines())
for x, c in enumerate(line)
if c == wall
}


def parse_numbers_split(s: str) -> list[int]:
Expand Down Expand Up @@ -261,3 +261,15 @@ def opposite(self) -> Direction4:

def apply(self, x: int, y: int, *, n: int = 1) -> tuple[int, int]:
return self.x * n + x, self.y * n + y

@staticmethod
def from_c(c: str) -> Direction4:
return _DIRECTION4_C[c]


_DIRECTION4_C = {
'<': Direction4.LEFT,
'>': Direction4.RIGHT,
'^': Direction4.UP,
'v': Direction4.DOWN,
}

0 comments on commit e98d271

Please sign in to comment.