Skip to content

Commit e795b83

Browse files
committed
Day 20 Part 1
1 parent 668a160 commit e795b83

File tree

5 files changed

+270
-0
lines changed

5 files changed

+270
-0
lines changed

Day_20/README.md

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
## \--- Day 20: Race Condition ---
2+
3+
The Historians are quite pixelated again. This time, a massive, black building looms over you - you're [right outside](https://adventofcode.com/2017/day/24) the CPU!
4+
5+
While The Historians get to work, a nearby program sees that you're idle and challenges you to a _race_. Apparently, you've arrived just in time for the frequently-held _race condition_ festival!
6+
7+
The race takes place on a particularly long and twisting code path; programs compete to see who can finish in the _fewest picoseconds_. The winner even gets their very own [mutex](https://en.wikipedia.org/wiki/Lock_\(computer_science\))!
8+
9+
They hand you a _map of the racetrack_ (your puzzle input). For example:
10+
11+
```
12+
###############
13+
#...#...#.....#
14+
#.#.#.#.#.###.#
15+
#S#...#.#.#...#
16+
#######.#.#.###
17+
#######.#.#...#
18+
#######.#.###.#
19+
###..E#...#...#
20+
###.#######.###
21+
#...###...#...#
22+
#.#####.#.###.#
23+
#.#...#.#.#...#
24+
#.#.#.#.#.#.###
25+
#...#...#...###
26+
###############
27+
```
28+
29+
The map consists of track (`.`) - including the _start_ (`S`) and _end_ (`E`) positions (both of which also count as track) - and _walls_ (`#`).
30+
31+
When a program runs through the racetrack, it starts at the start position. Then, it is allowed to move up, down, left, or right; each such move takes _1 picosecond_. The goal is to reach the end position as quickly as possible. In this example racetrack, the fastest time is `84` picoseconds.
32+
33+
Because there is only a single path from the start to the end and the programs all go the same speed, the races used to be pretty boring. To make things more interesting, they introduced a new rule to the races: programs are allowed to _cheat_.
34+
35+
The rules for cheating are very strict. _Exactly once_ during a race, a program may _disable collision_ for up to _2 picoseconds_. This allows the program to _pass through walls_ as if they were regular track. At the end of the cheat, the program must be back on normal track again; otherwise, it will receive a [segmentation fault](https://en.wikipedia.org/wiki/Segmentation_fault) and get disqualified.
36+
37+
So, a program could complete the course in 72 picoseconds (saving _12 picoseconds_) by cheating for the two moves marked `1` and `2`:
38+
39+
```
40+
###############
41+
#...#...12....#
42+
#.#.#.#.#.###.#
43+
#S#...#.#.#...#
44+
#######.#.#.###
45+
#######.#.#...#
46+
#######.#.###.#
47+
###..E#...#...#
48+
###.#######.###
49+
#...###...#...#
50+
#.#####.#.###.#
51+
#.#...#.#.#...#
52+
#.#.#.#.#.#.###
53+
#...#...#...###
54+
###############
55+
```
56+
57+
Or, a program could complete the course in 64 picoseconds (saving _20 picoseconds_) by cheating for the two moves marked `1` and `2`:
58+
59+
```
60+
###############
61+
#...#...#.....#
62+
#.#.#.#.#.###.#
63+
#S#...#.#.#...#
64+
#######.#.#.###
65+
#######.#.#...#
66+
#######.#.###.#
67+
###..E#...12..#
68+
###.#######.###
69+
#...###...#...#
70+
#.#####.#.###.#
71+
#.#...#.#.#...#
72+
#.#.#.#.#.#.###
73+
#...#...#...###
74+
###############
75+
```
76+
77+
This cheat saves _38 picoseconds_:
78+
79+
```
80+
###############
81+
#...#...#.....#
82+
#.#.#.#.#.###.#
83+
#S#...#.#.#...#
84+
#######.#.#.###
85+
#######.#.#...#
86+
#######.#.###.#
87+
###..E#...#...#
88+
###.####1##.###
89+
#...###.2.#...#
90+
#.#####.#.###.#
91+
#.#...#.#.#...#
92+
#.#.#.#.#.#.###
93+
#...#...#...###
94+
###############
95+
```
96+
97+
This cheat saves _64 picoseconds_ and takes the program directly to the end:
98+
99+
```
100+
###############
101+
#...#...#.....#
102+
#.#.#.#.#.###.#
103+
#S#...#.#.#...#
104+
#######.#.#.###
105+
#######.#.#...#
106+
#######.#.###.#
107+
###..21...#...#
108+
###.#######.###
109+
#...###...#...#
110+
#.#####.#.###.#
111+
#.#...#.#.#...#
112+
#.#.#.#.#.#.###
113+
#...#...#...###
114+
###############
115+
```
116+
117+
Each cheat has a distinct _start position_ (the position where the cheat is activated, just before the first move that is allowed to go through walls) and _end position_; cheats are uniquely identified by their start position and end position.
118+
119+
In this example, the total number of cheats (grouped by the amount of time they save) are as follows:
120+
121+
- There are 14 cheats that save 2 picoseconds.
122+
- There are 14 cheats that save 4 picoseconds.
123+
- There are 2 cheats that save 6 picoseconds.
124+
- There are 4 cheats that save 8 picoseconds.
125+
- There are 2 cheats that save 10 picoseconds.
126+
- There are 3 cheats that save 12 picoseconds.
127+
- There is one cheat that saves 20 picoseconds.
128+
- There is one cheat that saves 36 picoseconds.
129+
- There is one cheat that saves 38 picoseconds.
130+
- There is one cheat that saves 40 picoseconds.
131+
- There is one cheat that saves 64 picoseconds.
132+
133+
You aren't sure what the conditions of the racetrack will be like, so to give yourself as many options as possible, you'll need a list of the best cheats. _How many cheats would save you at least 100 picoseconds?_
134+
135+
Your puzzle answer was `[REDACTED]`.
136+
137+
The first half of this puzzle is complete! It provides one gold star: ⭐

Day_20/example_20a.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
###############
2+
#...#...#.....#
3+
#.#.#.#.#.###.#
4+
#S#...#.#.#...#
5+
#######.#.#.###
6+
#######.#.#...#
7+
#######.#.###.#
8+
###..E#...#...#
9+
###.#######.###
10+
#...###...#...#
11+
#.#####.#.###.#
12+
#.#...#.#.#...#
13+
#.#.#.#.#.#.###
14+
#...#...#...###
15+
###############

Day_20/task_20a.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import collections
2+
3+
4+
def load_map(filename):
5+
with open(filename, "r") as f:
6+
map_data = f.read()
7+
8+
return [list(row) for row in map_data.split()]
9+
10+
11+
def bfs(grid, start, end):
12+
height = len(grid)
13+
width = len(grid[0])
14+
end_x, end_y = end
15+
queue = collections.deque([[start]])
16+
visited = set([start])
17+
18+
while queue:
19+
path = queue.popleft()
20+
x, y = path[-1]
21+
if x == end_x and y == end_y:
22+
return path
23+
for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
24+
nx, ny = x + dx, y + dy
25+
if (
26+
0 <= nx < height
27+
and 0 <= ny < width
28+
and grid[nx][ny] != "#"
29+
and (nx, ny) not in visited
30+
):
31+
queue.append(path + [(nx, ny)])
32+
visited.add((nx, ny))
33+
return path
34+
35+
36+
def find_pos(grid, el):
37+
for row_ind, row in enumerate(grid):
38+
for col_ind, cell in enumerate(row):
39+
if cell == el:
40+
return (row_ind, col_ind)
41+
42+
43+
def is_within_bounds(point_x, point_y, max_row, max_col):
44+
return 0 <= point_x < max_row and 0 <= point_y < max_col
45+
46+
47+
def find_cheat_positions(racepath, field, max_row, max_col, threshold):
48+
start_ind = racepath.index(field)
49+
x, y = field
50+
51+
pos = set()
52+
directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
53+
for dx1, dy1 in directions:
54+
nx1, ny1 = x + dx1, y + dy1
55+
if is_within_bounds(nx1, ny1, max_row, max_col):
56+
for dx2, dy2 in directions:
57+
nx2, ny2 = nx1 + dx2, ny1 + dy2
58+
# check if is back on normal track again
59+
if (
60+
is_within_bounds(nx2, ny2, max_row, max_col)
61+
and (nx2, ny2) in racepath
62+
):
63+
end_ind = racepath.index((nx2, ny2))
64+
if start_ind - end_ind - 2 >= threshold:
65+
# we need to spend 2 picoseconds for the cheating
66+
pos.add(((x, y), (nx2, ny2)))
67+
return pos
68+
69+
70+
def cheat_threshold(racetrack, threshold):
71+
start = find_pos(racetrack, "S")
72+
end = find_pos(racetrack, "E")
73+
racepath = bfs(
74+
racetrack, start, end
75+
) # there is only a single path from the start to the end
76+
max_row = len(racetrack)
77+
max_col = len(racetrack[0])
78+
79+
pos = set()
80+
for field in racepath:
81+
pos.update(find_cheat_positions(racepath, field, max_row, max_col, threshold))
82+
83+
return pos
84+
85+
86+
if "__main__" == __name__:
87+
racetrack = load_map("Day_20/puzzle_input.txt")
88+
pos = cheat_threshold(racetrack, 100)
89+
print(len(pos))

Day_20/test_task_20a.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from task_20a import load_map, cheat_threshold
2+
3+
import pytest
4+
5+
6+
@pytest.mark.parametrize(
7+
"threshold,expected",
8+
[
9+
(64, 1),
10+
(40, 2),
11+
(38, 3),
12+
(36, 4),
13+
(20, 5),
14+
(12, 8),
15+
(10, 10),
16+
(8, 14),
17+
(6, 16),
18+
(4, 30),
19+
(2, 44),
20+
],
21+
)
22+
def test_cheat_threshold(threshold, expected):
23+
racetrack = load_map("Day_20/example_20a.txt")
24+
pos = cheat_threshold(racetrack, threshold)
25+
assert len(pos) == expected

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,7 @@ The [match](https://docs.python.org/3/tutorial/controlflow.html#tut-match) state
9595

9696

9797
Brute force was good enough, so I stuck with it. Apparently, you can practically reuse part 2 from day 16 (you just need to remove the condition about 1000 points → [🔗](https://www.reddit.com/r/adventofcode/comments/1hguacy/comment/m2q835j/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button)), but I don’t have that part ready yet, so we’ll see in the future 😅
98+
99+
## Day 20
100+
Part 1: I forgot that two picoseconds are needed for the cheating, so the distance isn’t simply the difference between the end position index and the start position index—we need to reduce it by 2.
101+

0 commit comments

Comments
 (0)