As you enter this room, you hear a loud click! Some of the tiles in the floor here seem to be pressure plates for traps, and the trap you just triggered has run out of... whatever it tried to do to you. You doubt you'll be so lucky next time.
Upon closer examination, the traps and safe tiles in this room seem to follow a pattern. The tiles are arranged into rows that are all the same width; you take note of the safe tiles (.)
and traps (^)
in the first row (your puzzle input).
The type of tile (trapped or safe) in each row is based on the types of the tiles in the same position, and to either side of that position, in the previous row. (If either side is off either end of the row, it counts as "safe" because there isn't a trap embedded in the wall.)
For example, suppose you know the first row (with tiles marked by letters) and want to determine the next row (with tiles marked by numbers):
ABCDE
12345
The type of tile 2
is based on the types of tiles A, B, and C
; the type of tile 5
is based on tiles D, E
, and an imaginary "safe"
tile. Let's call these three tiles from the previous row the left, center, and right
tiles, respectively. Then, a new tile is a trap only in one of the following situations:
In any other situation, the new tile is safe.
Then, starting with the row ..^^.
, you can determine the next row by applying those rules to each new tile:
(.)
, center (.)
, and right (^)
tiles from the previous row. This matches the fourth rule: only the right tile is a trap. Therefore, the next tile in this new row is a trap, ^
..^^
, which matches the second trap rule: its center and right tiles are traps, but its left tile is not. Therefore, this tile is also a trap, ^.^
.After these steps, we now know the next row of tiles in the room: .^^^^
. Then, we continue on to the next row, using the same rules, and get ^^..^
. After determining two new rows, our map looks like this:
..^^.
.^^^^
^^..^
Here's a larger example with ten
tiles per row and ten
rows:
.^^.^.^^^^
^^^...^..^
^.^^.^.^^.
..^^...^^^
.^^^^.^^.^
^^..^.^^..
^^^^..^^^.
^..^^^^.^^
.^^^..^.^^
^^.^^^..^^
In ten rows, this larger example has 38
safe tiles.
Starting with the map in your puzzle input, in a total of 40
rows (including the starting row), how many safe tiles are there?
This is a generation problem, where the generation of values is based on certain rules and/or inputs. In this case, the generation of a tile is based on the tiles around it in the previous row. Given the set of rules, we must generate a total of 40 rows and then count the 'safe' tiles.
Each tile to be generated is based on the three tiles above it - one directly above it, one to its left, and one to its right. Based on the combination of these three tiles, we determine whether the new tile is safe or a trap.
In [1]:
SAFE = '.'
TRAP = '^'
def is_safe(tile):
return tile == SAFE
def is_trap(tile):
return tile == TRAP
There are edge cases when generating tiles that are in the first or the last position. In the first position, the left upper tile does not exist, in which case, we assume it to be safe. Same for the upper right tile for the tile in the last position.
We create an accessor method to get us the correct tile even when the index is out of bounds. Here we assume that all tiles out of bounds are 'safe'.
In [2]:
def get_tile(row, index):
if 0 <= index < len(row):
return row[index]
return SAFE
There are four rules as specified by:
Which, when written in an alternate form using the functions we wrote, becomes:
1. is_trap(left) and is_trap(center) and is_safe(right)
2. is_safe(left) and is_trap(center) and is_trap(right)
3. is_trap(left) and is_safe(center) and is_safe(right)
4. is_safe(left) and is_safe(center) and is_trap(right)
A quick observation:
Therefore, we can summarize these rules as:
left == center == (not right) OR (not left) == center == right
Now whether the tiles are safe or traps, if they satisfy the given rules, then the new tile is a trap. Based on this, we create the make_tile
method.
In [3]:
def make_tile(previous_row, tile_index):
left = is_trap(get_tile(previous_row, tile_index - 1))
center = is_trap(get_tile(previous_row, tile_index))
right = is_trap(get_tile(previous_row, tile_index + 1))
if (left == center == (not right)) or ((not left) == center == right):
return TRAP
return SAFE
In [4]:
row = '..^^.'
print(row)
next_row = ''.join((make_tile(row, index) for index in range(len(row))))
print(next_row)
next_row = ''.join((make_tile(next_row, index) for index in range(len(next_row))))
print(next_row)
Generating the 10 x 10 test data
In [5]:
row = '.^^.^.^^^^'
print(row)
for _ in range(10 - 1):
row = ''.join((make_tile(row, index) for index in range(len(row))))
print(row)
In [6]:
with open('../inputs/Day18.txt', 'r') as f:
input_data = f.readline().strip()
In [7]:
safe_tiles = input_data.count(SAFE)
row = input_data
for _ in range(40 - 1):
row = ''.join((make_tile(row, index) for index in range(len(row))))
safe_tiles += row.count(SAFE)
print('answer', safe_tiles)
In [8]:
safe_tiles = input_data.count(SAFE)
row = input_data
for _ in range(400000 - 1):
row = ''.join((make_tile(row, index) for index in range(len(row))))
safe_tiles += row.count(SAFE)
print('answer', safe_tiles)
== END ==