171 lines
4.7 KiB
Python

# --- Day 3: Spiral Memory ---
# You come across an experimental new kind of memory stored on an infinite
# two-dimensional grid.
# Each square on the grid is allocated in a spiral pattern starting at a
# location marked 1 and then counting up while spiraling outward. For example,
# the first few squares are allocated like this:
# 17 16 15 14 13
# 18 5 4 3 12
# 19 6 1 2 11
# 20 7 8 9 10
# 21 22 23---> ...
# While this is very space-efficient (no squares are skipped), requested data
# must be carried back to square 1 (the location of the only access port for
# this memory system) by programs that can only move up, down, left, or right.
# They always take the shortest path: the Manhattan Distance between the
# location of the data and square 1.
# For example:
# Data from square 1 is carried 0 steps, since it's at the access port.
# Data from square 12 is carried 3 steps, such as: down, left, left.
# Data from square 23 is carried only 2 steps: up twice.
# Data from square 1024 must be carried 31 steps.
# How many steps are required to carry the data from the square identified in
# your puzzle input all the way to the access port?
# Your puzzle input is 347991.
from typing import Generator, Tuple
from scipy.spatial.distance import cityblock
# https://stackoverflow.com/questions/23706690/how-do-i-make-make-spiral-in-python
def move_right(x: int, y: int) -> Tuple[int, int]:
return x + 1, y
def move_down(x: int, y: int) -> Tuple[int, int]:
return x, y - 1
def move_left(x: int, y: int) -> Tuple[int, int]:
return x - 1, y
def move_up(x: int, y: int) -> Tuple[int, int]:
return x, y + 1
moves = [move_right, move_up, move_left, move_down]
def gen_points(end: int) -> Generator[Tuple[int, Tuple[int, int]], int, None]:
from itertools import cycle
_moves = cycle(moves)
n = 1
pos = 0, 0
times_to_move = 1
yield n, pos
while True:
for _ in range(2):
move = next(_moves)
for _ in range(times_to_move):
if n >= end:
return
pos = move(*pos)
n += 1
yield n, pos
times_to_move += 1
def part_1() -> None:
my_lst = list(gen_points(347991))
init = my_lst[0][1]
target = my_lst[347990][1]
print(f"We need {cityblock(init, target)} steps")
# --- Part Two ---
# As a stress test on the system, the programs here clear the grid and then
# store the value 1 in square 1. Then, in the same allocation order as shown
# above, they store the sum of the values in all adjacent squares, including
# diagonals.
# So, the first few squares' values are chosen as follows:
# Square 1 starts with the value 1.
# Square 2 has only one adjacent filled square (with value 1), so it also
# stores 1.
# Square 3 has both of the above squares as neighbors and stores the sum of
# their values, 2.
# Square 4 has all three of the aforementioned squares as neighbors and
# stores the sum of their values, 4.
# Square 5 only has the first and fourth squares as neighbors, so it gets
# the value 5.
# Once a square is written, its value does not change. Therefore, the first few
# squares would receive the following values:
# 147 142 133 122 59
# 304 5 4 2 57
# 330 10 1 1 54
# 351 11 23 25 26
# 362 747 806---> ...
# What is the first value written that is larger than your puzzle input?
def is_adjacent(p1: Tuple[int, int], p2: Tuple[int, int]) -> bool:
if (abs(p1[0] - p2[0]) == 0) and (abs(p1[1] - p2[1]) == 1):
return True
elif (abs(p1[0] - p2[0]) == 1) and (abs(p1[1] - p2[1]) == 0):
return True
elif (abs(p1[0] - p2[0]) == 1) and (abs(p1[1] - p2[1]) == 1):
return True
return False
def gen_points_adjacents(
end: int,
) -> Generator[Tuple[int, Tuple[int, int]], int, None]:
from itertools import cycle
_moves = cycle(moves)
n = 1
pos = 0, 0
times_to_move = 1
yield n, pos
saved_items = [(n, pos)]
while True:
for _ in range(2):
move = next(_moves)
for _ in range(times_to_move):
if n >= end:
return
pos = move(*pos)
adjacent_elements = []
for el in saved_items:
if is_adjacent(el[1], pos):
adjacent_elements.append(el[0])
n = sum(adjacent_elements)
saved_items.append((n, pos))
yield n, pos
times_to_move += 1
def part_2() -> None:
my_lst = list(gen_points_adjacents(347991))
print(f"The value we are looking for is {my_lst[-1:][0][0]}")
if __name__ == "__main__":
part_1()
part_2()