mirror of
https://github.com/Threnklyn/advent-of-code-go.git
synced 2026-05-18 19:13:27 +02:00
python solutions through day17
This commit is contained in:
@@ -0,0 +1,121 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
# index by index brute force with a sliding window optimization to make it linear...
|
||||||
|
def part1(input: str) -> int:
|
||||||
|
total_disk_space: int = 0
|
||||||
|
for x in list(input):
|
||||||
|
total_disk_space += int(x)
|
||||||
|
|
||||||
|
file_system: list[int] = [-1] * total_disk_space
|
||||||
|
is_file: bool = True
|
||||||
|
index: int = 0
|
||||||
|
file_number: int = 0
|
||||||
|
|
||||||
|
for x in list(input):
|
||||||
|
if not is_file:
|
||||||
|
index += int(x)
|
||||||
|
is_file = not is_file
|
||||||
|
else:
|
||||||
|
for _ in range(int(x)):
|
||||||
|
if is_file:
|
||||||
|
file_system[index] = file_number
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
file_number += 1
|
||||||
|
|
||||||
|
is_file = not is_file
|
||||||
|
|
||||||
|
# rearrange file to left via sliding window
|
||||||
|
left: int = 0
|
||||||
|
right: int = len(file_system) - 1
|
||||||
|
while left < right:
|
||||||
|
if file_system[right] == -1:
|
||||||
|
right -= 1
|
||||||
|
elif file_system[left] != -1:
|
||||||
|
left += 1
|
||||||
|
elif file_system[left] == -1:
|
||||||
|
file_system[left], file_system[right] = (
|
||||||
|
file_system[right],
|
||||||
|
file_system[left],
|
||||||
|
)
|
||||||
|
left += 1
|
||||||
|
|
||||||
|
# checksum is index in string * number value (file number)
|
||||||
|
checksum: int = 0
|
||||||
|
for i in range(len(file_system)):
|
||||||
|
if file_system[i] == -1:
|
||||||
|
break
|
||||||
|
checksum += i * file_system[i]
|
||||||
|
|
||||||
|
return checksum
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class FileSystemSpace:
|
||||||
|
start: int
|
||||||
|
size: int
|
||||||
|
file_number: int
|
||||||
|
|
||||||
|
|
||||||
|
def part2(input: str) -> int:
|
||||||
|
files: list[FileSystemSpace] = []
|
||||||
|
empty_spaces: list[FileSystemSpace] = []
|
||||||
|
|
||||||
|
is_file: bool = True
|
||||||
|
index: int = 0
|
||||||
|
for size in [int(x) for x in list(input)]:
|
||||||
|
file_or_empty = FileSystemSpace(index, size, len(files))
|
||||||
|
index += size
|
||||||
|
if is_file:
|
||||||
|
files.append(file_or_empty)
|
||||||
|
else:
|
||||||
|
empty_spaces.append(file_or_empty)
|
||||||
|
is_file = not is_file
|
||||||
|
|
||||||
|
# brute force finding a space to move each file into
|
||||||
|
for file in reversed(files):
|
||||||
|
for empty_space in empty_spaces:
|
||||||
|
# prevent moving files to higher spots in the file system...
|
||||||
|
if empty_space.start > file.start:
|
||||||
|
break
|
||||||
|
|
||||||
|
# large enough empty space found
|
||||||
|
if empty_space.size >= file.size:
|
||||||
|
file.start = empty_space.start
|
||||||
|
empty_space.start += file.size
|
||||||
|
empty_space.size -= file.size
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
# print_util(files)
|
||||||
|
|
||||||
|
checksum: int = 0
|
||||||
|
for file in files:
|
||||||
|
for x in range(file.size):
|
||||||
|
checksum += (file.start + x) * file.file_number
|
||||||
|
|
||||||
|
return checksum
|
||||||
|
|
||||||
|
|
||||||
|
def print_util(all_files: list[FileSystemSpace]):
|
||||||
|
last_index: int = 0
|
||||||
|
for file in all_files:
|
||||||
|
last_index = max(last_index, file.start + file.size)
|
||||||
|
|
||||||
|
fs: list[str] = ["."] * (last_index + 1)
|
||||||
|
for file in all_files:
|
||||||
|
for i in range(file.start, file.start + file.size):
|
||||||
|
fs[i] = str(file.file_number)
|
||||||
|
|
||||||
|
print("".join(fs))
|
||||||
|
|
||||||
|
|
||||||
|
example = """2333133121414131402"""
|
||||||
|
input = open("input.txt").read().strip()
|
||||||
|
|
||||||
|
print(f"part1 example: {part1(example)} want 1928")
|
||||||
|
print(f"part1: {part1(input)} want 6200294120911")
|
||||||
|
|
||||||
|
print(f"part2 example: {part2(example)} want 2858")
|
||||||
|
print(f"part2: {part2(input)} want 6227018762750")
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
def part1(input: str) -> int:
|
||||||
|
grid: list[list[int, int]] = []
|
||||||
|
for line in input.splitlines():
|
||||||
|
grid.append([int(x) for x in list(line)])
|
||||||
|
# alternative pythonic way...
|
||||||
|
# grid = [[int(char) for char in line] for line in input.splitlines()]
|
||||||
|
|
||||||
|
ans: int = 0
|
||||||
|
for r in range(len(grid)):
|
||||||
|
for c in range(len(grid[0])):
|
||||||
|
if grid[r][c] == 0:
|
||||||
|
ans += len(dfs_backtrack_unique_end_coords(grid, r, c, {(r, c)}))
|
||||||
|
|
||||||
|
return ans
|
||||||
|
|
||||||
|
|
||||||
|
# I misread the instructions slightly and went for a "unique paths" algo at first.
|
||||||
|
# It would be simpler to pass the "reachable 9s coords" set in as an arg and update
|
||||||
|
# it in the termination case, then take the length of that set in the part1() function.
|
||||||
|
# This would remove the need to combine the sets which is potentially expensive (and ugly)
|
||||||
|
def dfs_backtrack_unique_end_coords(
|
||||||
|
grid: list[list[int, int]], row: int, col: int, visited: set[tuple[int, int]]
|
||||||
|
) -> set[tuple[int, int]]:
|
||||||
|
if grid[row][col] == 9:
|
||||||
|
return {(row, col)}
|
||||||
|
|
||||||
|
all_coords: set[tuple[int, int]] = set()
|
||||||
|
for diff in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
|
||||||
|
nextRow = row + diff[0]
|
||||||
|
nextCol = col + diff[1]
|
||||||
|
|
||||||
|
if not (0 <= nextRow < len(grid) and 0 <= nextCol < len(grid[0])):
|
||||||
|
continue
|
||||||
|
if (nextRow, nextCol) in visited:
|
||||||
|
continue
|
||||||
|
if grid[nextRow][nextCol] == grid[row][col] + 1:
|
||||||
|
visited.add((nextRow, nextCol))
|
||||||
|
new_coords = dfs_backtrack_unique_end_coords(
|
||||||
|
grid, nextRow, nextCol, visited
|
||||||
|
)
|
||||||
|
# combines the two sets, see comment above function def
|
||||||
|
all_coords.update(new_coords)
|
||||||
|
visited.remove((nextRow, nextCol))
|
||||||
|
|
||||||
|
return all_coords
|
||||||
|
|
||||||
|
|
||||||
|
def part2(input: str) -> int:
|
||||||
|
grid = [[int(char) for char in line] for line in input.splitlines()]
|
||||||
|
|
||||||
|
ans: int = 0
|
||||||
|
for r in range(len(grid)):
|
||||||
|
for c in range(len(grid[0])):
|
||||||
|
if grid[r][c] == 0:
|
||||||
|
ans += dfs_backtrack_unique_paths(grid, r, c, {(r, c)})
|
||||||
|
|
||||||
|
return ans
|
||||||
|
|
||||||
|
|
||||||
|
def dfs_backtrack_unique_paths(
|
||||||
|
grid: list[list[int, int]], row: int, col: int, visited: set[tuple[int, int]]
|
||||||
|
) -> int:
|
||||||
|
if grid[row][col] == 9:
|
||||||
|
# unique path found
|
||||||
|
return 1
|
||||||
|
|
||||||
|
total: int = 0
|
||||||
|
for diff in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
|
||||||
|
nextRow = row + diff[0]
|
||||||
|
nextCol = col + diff[1]
|
||||||
|
|
||||||
|
if not (0 <= nextRow < len(grid) and 0 <= nextCol < len(grid[0])):
|
||||||
|
continue
|
||||||
|
if (nextRow, nextCol) in visited:
|
||||||
|
continue
|
||||||
|
if grid[nextRow][nextCol] == grid[row][col] + 1:
|
||||||
|
visited.add((nextRow, nextCol))
|
||||||
|
total += dfs_backtrack_unique_paths(grid, nextRow, nextCol, visited)
|
||||||
|
visited.remove((nextRow, nextCol))
|
||||||
|
|
||||||
|
return total
|
||||||
|
|
||||||
|
|
||||||
|
example = """89010123
|
||||||
|
78121874
|
||||||
|
87430965
|
||||||
|
96549874
|
||||||
|
45678903
|
||||||
|
32019012
|
||||||
|
01329801
|
||||||
|
10456732"""
|
||||||
|
|
||||||
|
input = open("input.txt").read().strip()
|
||||||
|
|
||||||
|
print(f"part1 example: {part1(example)} want 36")
|
||||||
|
print(f"part1: {part1(input)} want 782")
|
||||||
|
|
||||||
|
print(f"part2 example: {part2(example)} want 81")
|
||||||
|
print(f"part2 example: {part2(input)} want 1694")
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# If the stone is engraved with the number 0, it is replaced by a stone engraved with the number 1.
|
||||||
|
# If the stone is engraved with a number that has an even number of digits, it is replaced by two stones. The left half of the digits are engraved on the new left stone, and the right half of the digits are engraved on the new right stone. (The new numbers don't keep extra leading zeroes: 1000 would become stones 10 and 0.)
|
||||||
|
# If none of the other rules apply, the stone is replaced by a new stone; the old stone's number multiplied by 2024 is engraved on the new stone.
|
||||||
|
|
||||||
|
|
||||||
|
# actually modelling this would be a pain in the ass, error-prone, and probably
|
||||||
|
# not fast enough for part 2 where there will presumably be more blinks
|
||||||
|
# instead if we only care about the final number of stones, we can just see
|
||||||
|
# how many stone each original stone splits into, and we can memoize it
|
||||||
|
def day11(input: str, blinks: int) -> int:
|
||||||
|
total_stones: int = 0
|
||||||
|
memo: dict[tuple[int, int], int] = {}
|
||||||
|
for stone in input.split(" "):
|
||||||
|
total_stones += calculate_final_stones_count(stone, blinks, memo)
|
||||||
|
return total_stones
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_final_stones_count(num_as_str: str, blinks_left: int, memo) -> int:
|
||||||
|
key = (num_as_str, blinks_left)
|
||||||
|
if key in memo:
|
||||||
|
return memo[key]
|
||||||
|
|
||||||
|
if blinks_left == 0:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
total_stones: int = 0
|
||||||
|
if num_as_str == "0":
|
||||||
|
total_stones += calculate_final_stones_count("1", blinks_left - 1, memo)
|
||||||
|
elif len(num_as_str) % 2 == 0:
|
||||||
|
# convert back and forth again to get rid of leading zeroes
|
||||||
|
left_num = str(int(num_as_str[: len(num_as_str) // 2]))
|
||||||
|
right_num = str(int(num_as_str[len(num_as_str) // 2 :]))
|
||||||
|
total_stones += calculate_final_stones_count(left_num, blinks_left - 1, memo)
|
||||||
|
total_stones += calculate_final_stones_count(right_num, blinks_left - 1, memo)
|
||||||
|
else:
|
||||||
|
new_num: int = str(int(num_as_str) * 2024)
|
||||||
|
total_stones += calculate_final_stones_count(new_num, blinks_left - 1, memo)
|
||||||
|
|
||||||
|
memo[key] = total_stones
|
||||||
|
|
||||||
|
return total_stones
|
||||||
|
|
||||||
|
|
||||||
|
example = """125 17"""
|
||||||
|
input = open("input.txt").read().strip()
|
||||||
|
|
||||||
|
print(f"part1 example: {day11(example, 6)} want 22")
|
||||||
|
print(f"part1 example: {day11(example, 25)} want 55312")
|
||||||
|
print(f"part1: {day11(input, 25)} want 189092")
|
||||||
|
print(f"part2: {day11(input, 75)} want 224869647102559")
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
|
||||||
|
def day12(input: str, part: int) -> int:
|
||||||
|
|
||||||
|
grid = [list(line) for line in input.splitlines()]
|
||||||
|
visited: set[tuple[int, int]] = {}
|
||||||
|
cost: int = 0
|
||||||
|
|
||||||
|
for r in range(len(grid)):
|
||||||
|
for c in range(len(grid[0])):
|
||||||
|
coord = (r, c)
|
||||||
|
if coord not in visited:
|
||||||
|
island_coords: set[tuple[int, int]] = set()
|
||||||
|
|
||||||
|
flood_fill_island(grid, r, c, visited, island_coords)
|
||||||
|
if part == 1:
|
||||||
|
cost += len(island_coords) * get_perimeter_of_island(island_coords)
|
||||||
|
elif part == 2:
|
||||||
|
edge_count: int = get_edge_count_of_island(island_coords)
|
||||||
|
cost += len(island_coords) * edge_count
|
||||||
|
else:
|
||||||
|
raise ("unexpected part")
|
||||||
|
|
||||||
|
return cost
|
||||||
|
|
||||||
|
|
||||||
|
# refactored to just populate the entire island_coords set and visited set
|
||||||
|
# just populates the island_coords so does not need to return anything
|
||||||
|
def flood_fill_island(
|
||||||
|
grid: list[list[str]],
|
||||||
|
row: int,
|
||||||
|
col: int,
|
||||||
|
visited: set[tuple[int, int]],
|
||||||
|
island_coords: set[tuple[int, int]],
|
||||||
|
):
|
||||||
|
visited[(row, col)] = True
|
||||||
|
island_coords.add((row, col))
|
||||||
|
|
||||||
|
for diff in [(0, -1), (0, 1), (-1, 0), (1, 0)]:
|
||||||
|
next_row = row + diff[0]
|
||||||
|
next_col = col + diff[1]
|
||||||
|
|
||||||
|
if 0 <= next_row < len(grid) and 0 <= next_col < len(grid[0]):
|
||||||
|
if (next_row, next_col) in visited:
|
||||||
|
continue
|
||||||
|
# if in range, check if neighbor is same to recurse
|
||||||
|
if grid[next_row][next_col] == grid[row][col]:
|
||||||
|
# if does match and unvisited, recurse
|
||||||
|
flood_fill_island(grid, next_row, next_col, visited, island_coords)
|
||||||
|
|
||||||
|
|
||||||
|
# for part1 cost calculation
|
||||||
|
def get_perimeter_of_island(island_coords: set[tuple[int, int]]) -> int:
|
||||||
|
perimeter: int = 0
|
||||||
|
for coord in island_coords:
|
||||||
|
for diff in [(0, -1), (0, 1), (-1, 0), (1, 0)]:
|
||||||
|
next_coord = (coord[0] + diff[0], coord[1] + diff[1])
|
||||||
|
if next_coord not in island_coords:
|
||||||
|
perimeter += 1
|
||||||
|
|
||||||
|
return perimeter
|
||||||
|
|
||||||
|
|
||||||
|
def get_edge_count_of_island(island_coords: set[tuple[int, int]]) -> int:
|
||||||
|
edges: int = 0
|
||||||
|
|
||||||
|
map_dir_to_coord: dict[tuple[int, int], set[tuple[int, int]]] = defaultdict(set)
|
||||||
|
|
||||||
|
for coord in island_coords:
|
||||||
|
for diff in [(0, -1), (0, 1), (-1, 0), (1, 0)]:
|
||||||
|
# this coord has already been accounted for in an edge
|
||||||
|
if coord in map_dir_to_coord[diff]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
next_coord = (coord[0] + diff[0], coord[1] + diff[1])
|
||||||
|
# not in the island means it is bordering an edge...
|
||||||
|
if next_coord not in island_coords:
|
||||||
|
# collect all coords that make up this same edge, basically we need to go perpendicular to diff
|
||||||
|
collect_all_coords_on_edge(island_coords, coord, diff, map_dir_to_coord)
|
||||||
|
edges += 1
|
||||||
|
|
||||||
|
return edges
|
||||||
|
|
||||||
|
|
||||||
|
perpendicular_dirs: dict[tuple[int, int], list[tuple[int, int]]] = {
|
||||||
|
(0, -1): [(-1, 0), (1, 0)],
|
||||||
|
(0, 1): [(-1, 0), (1, 0)],
|
||||||
|
(-1, 0): [(0, -1), (0, 1)],
|
||||||
|
(1, 0): [(0, -1), (0, 1)],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def collect_all_coords_on_edge(
|
||||||
|
island_coords: set[tuple[int, int]],
|
||||||
|
coord: tuple[int, int],
|
||||||
|
empty_dir_diff: tuple[int, int],
|
||||||
|
map_dir_to_coord: dict[tuple[int, int], set[tuple[int, int]]],
|
||||||
|
):
|
||||||
|
# mark self
|
||||||
|
map_dir_to_coord[empty_dir_diff].add(coord)
|
||||||
|
|
||||||
|
for perp in perpendicular_dirs[empty_dir_diff]:
|
||||||
|
next_coord = (coord[0] + perp[0], coord[1] + perp[1])
|
||||||
|
# we're collecting the entire connected edge that is facing a single direction,
|
||||||
|
# so stop checking if we're "off" the island
|
||||||
|
# do not need to check if we're inside the grid because we can just leverage island_coords
|
||||||
|
if next_coord not in island_coords:
|
||||||
|
continue
|
||||||
|
# if already visited for facing this direction, we can also skip
|
||||||
|
if next_coord in map_dir_to_coord[empty_dir_diff]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# continue if next_coord is not a contiguous part of the edge
|
||||||
|
if (next_coord[0] + empty_dir_diff[0], next_coord[1] + empty_dir_diff[1]) in island_coords:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# need to continue exploring recursively
|
||||||
|
collect_all_coords_on_edge(
|
||||||
|
island_coords, next_coord, empty_dir_diff, map_dir_to_coord
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
small_example = """AAAA
|
||||||
|
BBCD
|
||||||
|
BBCC
|
||||||
|
EEEC"""
|
||||||
|
|
||||||
|
|
||||||
|
example = """RRRRIICCFF
|
||||||
|
RRRRIICCCF
|
||||||
|
VVRRRCCFFF
|
||||||
|
VVRCCCJFFF
|
||||||
|
VVVVCJJCFE
|
||||||
|
VVIVCCJJEE
|
||||||
|
VVIIICJJEE
|
||||||
|
MIIIIIJJEE
|
||||||
|
MIIISIJEEE
|
||||||
|
MMMISSJEEE"""
|
||||||
|
|
||||||
|
part2_example = """AAAAAA
|
||||||
|
AAABBA
|
||||||
|
AAABBA
|
||||||
|
ABBAAA
|
||||||
|
ABBAAA
|
||||||
|
AAAAAA"""
|
||||||
|
|
||||||
|
input = open("input.txt").read().strip()
|
||||||
|
|
||||||
|
print(f"part1 small_example: {day12(small_example, 1)} want 140")
|
||||||
|
print(f"part1 example: {day12(example, 1)} want 1930")
|
||||||
|
print(f"part1: {day12(input, 1)} want 1473408")
|
||||||
|
|
||||||
|
print(f"part2 small_example: {day12(small_example, 2)} want 80")
|
||||||
|
# this one helped debug the disjointed edges bug (second and fourth rows pointing east)
|
||||||
|
print(f"part2 example: {day12("""EEEEE
|
||||||
|
EXXXX
|
||||||
|
EEEEE
|
||||||
|
EXXXX
|
||||||
|
EEEEE""", 2)} want 236")
|
||||||
|
print(f"part2 part2_example: {day12(part2_example, 2)} want 368")
|
||||||
|
print(f"part2 example: {day12(example, 2)} want 1206")
|
||||||
|
print(f"part2: {day12(input, 2)} want 886364")
|
||||||
|
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
def part1(input: str, part: int) -> int:
|
||||||
|
ans: int = 0
|
||||||
|
|
||||||
|
for machine in input.split("\n\n"):
|
||||||
|
lines = machine.split("\n")
|
||||||
|
a_parts: list[str] = re.findall(r"\d+", lines[0])
|
||||||
|
Ax, Ay = int(a_parts[0]), int(a_parts[1])
|
||||||
|
|
||||||
|
b_parts: list[str] = re.findall(r"\d+", lines[1])
|
||||||
|
Bx, By = int(b_parts[0]), int(b_parts[1])
|
||||||
|
|
||||||
|
prize_parts: list[str] = re.findall(r"\d+", lines[2])
|
||||||
|
Px, Py = int(prize_parts[0]), int(prize_parts[1])
|
||||||
|
|
||||||
|
if part == 2:
|
||||||
|
Px += 10000000000000
|
||||||
|
Py += 10000000000000
|
||||||
|
|
||||||
|
# Ax * a + Bx * b = Px
|
||||||
|
# Ay * a + By * b = Py
|
||||||
|
# Solve for a and b...
|
||||||
|
# a = (Px - Bx * b) / Ax
|
||||||
|
# b * (By * Ax - Ay * Bx) = Py * Ax - Ay * Px
|
||||||
|
b = (Py * Ax - Ay * Px) / (By * Ax - Ay * Bx)
|
||||||
|
a = (Px - Bx * b) / Ax
|
||||||
|
|
||||||
|
if b % 1 == 0 and a % 1 == 0:
|
||||||
|
ans += 3 * a + b
|
||||||
|
|
||||||
|
return int(ans)
|
||||||
|
|
||||||
|
|
||||||
|
# A 3, B 1
|
||||||
|
# moves right along X, forward along Y
|
||||||
|
|
||||||
|
example = """Button A: X+94, Y+34
|
||||||
|
Button B: X+22, Y+67
|
||||||
|
Prize: X=8400, Y=5400
|
||||||
|
|
||||||
|
Button A: X+26, Y+66
|
||||||
|
Button B: X+67, Y+21
|
||||||
|
Prize: X=12748, Y=12176
|
||||||
|
|
||||||
|
Button A: X+17, Y+86
|
||||||
|
Button B: X+84, Y+37
|
||||||
|
Prize: X=7870, Y=6450
|
||||||
|
|
||||||
|
Button A: X+69, Y+23
|
||||||
|
Button B: X+27, Y+71
|
||||||
|
Prize: X=18641, Y=10279"""
|
||||||
|
|
||||||
|
input = open("input.txt").read().strip()
|
||||||
|
|
||||||
|
print(f"part1 example: {part1(example, 1)} want 480")
|
||||||
|
print(f"part1: {part1(input, 1)} want 28059")
|
||||||
|
|
||||||
|
# don't think this result was given in the prompt
|
||||||
|
print(f"part1 example: {part1(example, 2)} want 875318608908")
|
||||||
|
print(f"part1: {part1(input, 2)} want 102255878088512")
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
def part1(input: str, wide: int, tall: int, seconds: int) -> int:
|
||||||
|
# don't need to model this exactly right? just equate a line, mod by grid size...
|
||||||
|
# i guess for 100 steps it would've been better to just model it...
|
||||||
|
|
||||||
|
quad_counts: list[int] = [0] * 4
|
||||||
|
|
||||||
|
for line in input.splitlines():
|
||||||
|
nums = re.findall(r"-?\d+", line)
|
||||||
|
assert len(nums) == 4
|
||||||
|
|
||||||
|
x = int(nums[0]) + int(nums[2]) * seconds
|
||||||
|
y = int(nums[1]) + int(nums[3]) * seconds
|
||||||
|
|
||||||
|
x %= tall
|
||||||
|
y %= wide
|
||||||
|
|
||||||
|
if x < tall // 2 and y < wide // 2:
|
||||||
|
# print("top left")
|
||||||
|
quad_counts[0] += 1
|
||||||
|
elif x < tall // 2 and y > wide // 2:
|
||||||
|
# print("top right")
|
||||||
|
quad_counts[1] += 1
|
||||||
|
elif x > tall // 2 and y < wide // 2:
|
||||||
|
# print("bottom left")
|
||||||
|
quad_counts[2] += 1
|
||||||
|
elif x > tall // 2 and y > wide // 2:
|
||||||
|
# print("bottom right")
|
||||||
|
quad_counts[3] += 1
|
||||||
|
# else:
|
||||||
|
# print("on mid line")
|
||||||
|
|
||||||
|
# multiply robot count in each quadrant
|
||||||
|
# does not include robots on mid-lines
|
||||||
|
return quad_counts[0] * quad_counts[1] * quad_counts[2] * quad_counts[3]
|
||||||
|
|
||||||
|
|
||||||
|
def part2(input: str, wide: int, tall: int) -> int:
|
||||||
|
# i guess for 100 steps it would've been better to just model it...
|
||||||
|
# then i could have reused it for part 2...
|
||||||
|
|
||||||
|
robots: list[list[int]] = []
|
||||||
|
for line in input.splitlines():
|
||||||
|
nums = re.findall(r"-?\d+", line)
|
||||||
|
assert len(nums) == 4
|
||||||
|
robots.append([int(x) for x in nums])
|
||||||
|
|
||||||
|
for s in range(10_000):
|
||||||
|
|
||||||
|
neighbors: set[tuple[int, int]] = set()
|
||||||
|
matched: set[tuple[int, int]] = set()
|
||||||
|
for robot in robots:
|
||||||
|
robot[0] += robot[2]
|
||||||
|
robot[1] += robot[3]
|
||||||
|
|
||||||
|
robot[0] %= tall
|
||||||
|
robot[1] %= wide
|
||||||
|
|
||||||
|
# track how many neighboring robots have been placed so far
|
||||||
|
# when the number is high enough we probably have some image of a tree
|
||||||
|
coord = (robot[0], robot[1])
|
||||||
|
if coord in neighbors:
|
||||||
|
matched.add(coord)
|
||||||
|
for dx in [-1, 0, 1]:
|
||||||
|
for dy in [-1, 0, 1]:
|
||||||
|
neighbors.add((coord[0] + dx, coord[1] + dy))
|
||||||
|
|
||||||
|
if len(matched) >= 250:
|
||||||
|
print_grid(robots, wide, tall)
|
||||||
|
# print(len(matched))
|
||||||
|
return s + 1
|
||||||
|
|
||||||
|
raise Exception("should return from loop")
|
||||||
|
|
||||||
|
|
||||||
|
def print_grid(robots: list[list[int]], wide: int, tall: int):
|
||||||
|
grid: list[list[str]] = []
|
||||||
|
for _ in range(0, tall):
|
||||||
|
grid.append([" "] * wide)
|
||||||
|
|
||||||
|
for robot in robots:
|
||||||
|
grid[robot[0]][robot[1]] = "X"
|
||||||
|
|
||||||
|
for line in grid:
|
||||||
|
print("".join(line))
|
||||||
|
|
||||||
|
|
||||||
|
example = """p=0,4 v=3,-3
|
||||||
|
p=6,3 v=-1,-3
|
||||||
|
p=10,3 v=-1,2
|
||||||
|
p=2,0 v=2,-1
|
||||||
|
p=0,0 v=1,3
|
||||||
|
p=3,0 v=-2,-2
|
||||||
|
p=7,6 v=-1,-3
|
||||||
|
p=3,0 v=-1,-2
|
||||||
|
p=9,3 v=2,3
|
||||||
|
p=7,3 v=-1,2
|
||||||
|
p=2,4 v=2,-3
|
||||||
|
p=9,5 v=-3,-3"""
|
||||||
|
|
||||||
|
input = open("input.txt").read().strip()
|
||||||
|
|
||||||
|
print(f"part1 example: {part1(example, 7, 11, 100)} want 12")
|
||||||
|
print(f"part1 example: {part1(input, 103, 101, 100)} want 218965032")
|
||||||
|
|
||||||
|
print(f"part2: {part2(input, 103, 101)} want 7037")
|
||||||
@@ -0,0 +1,214 @@
|
|||||||
|
diff_map: dict[str, tuple[int, int]] = {
|
||||||
|
"^": (-1, 0),
|
||||||
|
"v": (1, 0),
|
||||||
|
"<": (0, -1),
|
||||||
|
">": (0, 1),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def part1(input: str) -> int:
|
||||||
|
input_parts = input.split("\n\n")
|
||||||
|
grid = [list(line) for line in input_parts[0].splitlines()]
|
||||||
|
|
||||||
|
row: int = 0
|
||||||
|
col: int = 0
|
||||||
|
|
||||||
|
for r in range(len(grid)):
|
||||||
|
for c in range(len(grid[0])):
|
||||||
|
if grid[r][c] == "@":
|
||||||
|
row = r
|
||||||
|
col = c
|
||||||
|
|
||||||
|
instructions = "".join(input_parts[1].splitlines())
|
||||||
|
for inst in instructions:
|
||||||
|
diff = diff_map[inst]
|
||||||
|
next_row, next_col = row + diff[0], col + diff[1]
|
||||||
|
|
||||||
|
match grid[next_row][next_col]:
|
||||||
|
case "#":
|
||||||
|
# blocked
|
||||||
|
continue
|
||||||
|
case ".":
|
||||||
|
grid[row][col] = "."
|
||||||
|
grid[next_row][next_col] = "@"
|
||||||
|
row, col = next_row, next_col
|
||||||
|
case "O":
|
||||||
|
# attempt push, keep moving in direction of diff until a . or # is hit...
|
||||||
|
not_obstacle_row, not_obstacle_col = next_row, next_col
|
||||||
|
while grid[not_obstacle_row][not_obstacle_col] == "O":
|
||||||
|
not_obstacle_row += diff[0]
|
||||||
|
not_obstacle_col += diff[1]
|
||||||
|
|
||||||
|
# if it's a wall "#", nothing moves, so only check for empty spaces "."
|
||||||
|
if grid[not_obstacle_row][not_obstacle_col] == ".":
|
||||||
|
grid[not_obstacle_row][not_obstacle_col] = "O"
|
||||||
|
grid[next_row][next_col] = "@"
|
||||||
|
grid[row][col] = "."
|
||||||
|
row, col = next_row, next_col
|
||||||
|
case _:
|
||||||
|
raise Exception("unhandled grid type: ", grid[next_row][next_col])
|
||||||
|
|
||||||
|
# print("\n".join(["".join(row) for row in grid]))
|
||||||
|
|
||||||
|
# 100 times its distance from the top edge of the map plus its distance from the left edge of the map
|
||||||
|
# 0-indexed so same as indices in 2D array
|
||||||
|
gps_sum: int = 0
|
||||||
|
for r in range(len(grid)):
|
||||||
|
for c in range(len(grid[0])):
|
||||||
|
if grid[r][c] == "O":
|
||||||
|
gps_sum += r * 100 + c
|
||||||
|
|
||||||
|
return gps_sum
|
||||||
|
|
||||||
|
|
||||||
|
def part2(input: str) -> int:
|
||||||
|
input_parts = input.split("\n\n")
|
||||||
|
|
||||||
|
input_parts[0] = input_parts[0].replace("O", "[]")
|
||||||
|
input_parts[0] = input_parts[0].replace(".", "..")
|
||||||
|
input_parts[0] = input_parts[0].replace("#", "##")
|
||||||
|
input_parts[0] = input_parts[0].replace("@", "@.")
|
||||||
|
|
||||||
|
grid = [list(line) for line in input_parts[0].splitlines()]
|
||||||
|
|
||||||
|
row: int = 0
|
||||||
|
col: int = 0
|
||||||
|
|
||||||
|
for r in range(len(grid)):
|
||||||
|
for c in range(len(grid[0])):
|
||||||
|
if grid[r][c] == "@":
|
||||||
|
row = r
|
||||||
|
col = c
|
||||||
|
|
||||||
|
# print("\n".join(["".join(row) for row in grid]))
|
||||||
|
|
||||||
|
instructions = "".join(input_parts[1].splitlines())
|
||||||
|
for inst in instructions:
|
||||||
|
# first see what's in the next space we want to move into...
|
||||||
|
diff = diff_map[inst]
|
||||||
|
next_row, next_col = row + diff[0], col + diff[1]
|
||||||
|
match grid[next_row][next_col]:
|
||||||
|
case "#":
|
||||||
|
continue
|
||||||
|
case ".":
|
||||||
|
grid[row][col] = "."
|
||||||
|
row += diff[0]
|
||||||
|
col += diff[1]
|
||||||
|
grid[row][col] = "@"
|
||||||
|
case "[" | "]":
|
||||||
|
# handle left and right separately because they only push one row of boxes
|
||||||
|
# at this point row == next_row
|
||||||
|
if inst in "<>":
|
||||||
|
# copy-pasta from part 1
|
||||||
|
not_obstacle_col = next_col
|
||||||
|
while grid[next_row][not_obstacle_col] in "[]":
|
||||||
|
not_obstacle_col += diff[1]
|
||||||
|
|
||||||
|
# if it's a wall "#", nothing moves, so only check for empty spaces "."
|
||||||
|
# move everything over towards the diff direction
|
||||||
|
if grid[next_row][not_obstacle_col] == ".":
|
||||||
|
for c in range(not_obstacle_col, col, -1 * diff[1]):
|
||||||
|
grid[row][c] = grid[row][c - diff[1]]
|
||||||
|
|
||||||
|
# move robot..
|
||||||
|
grid[next_row][next_col] = "@"
|
||||||
|
grid[row][col] = "."
|
||||||
|
row, col = next_row, next_col
|
||||||
|
else:
|
||||||
|
# push boxes up or down, use a stack to maintain the reverse order of boxes that
|
||||||
|
# MAY get moved
|
||||||
|
coords_to_check: list[tuple[int, int]] = [
|
||||||
|
(row, col),
|
||||||
|
]
|
||||||
|
|
||||||
|
to_check_index: int = 0
|
||||||
|
is_blocked: bool = False
|
||||||
|
while to_check_index < len(coords_to_check):
|
||||||
|
front = coords_to_check[to_check_index]
|
||||||
|
to_check_index += 1
|
||||||
|
|
||||||
|
# variable masking is no bueno, but i guess it's fine here
|
||||||
|
next_row, next_col = front[0] + diff[0], front[1] + diff[1]
|
||||||
|
next_value = grid[next_row][next_col]
|
||||||
|
if next_value == "#":
|
||||||
|
is_blocked = True
|
||||||
|
break
|
||||||
|
if next_value == "[":
|
||||||
|
coords_to_check.append((next_row, next_col))
|
||||||
|
coords_to_check.append((next_row, next_col + 1))
|
||||||
|
if next_value == "]":
|
||||||
|
coords_to_check.append((next_row, next_col))
|
||||||
|
coords_to_check.append((next_row, next_col - 1))
|
||||||
|
# if "." then do nothing... no need to check
|
||||||
|
|
||||||
|
# move everything towards diff if nothing was blocked
|
||||||
|
# in reverse order to avoid overwrites
|
||||||
|
if not is_blocked:
|
||||||
|
# didn't prevent duplicate adds in the "checking" stage, so just no-op them here
|
||||||
|
moved_set: set[tuple[int, int]] = set()
|
||||||
|
for coord in reversed(coords_to_check):
|
||||||
|
if coord in moved_set:
|
||||||
|
continue
|
||||||
|
moved_set.add(coord)
|
||||||
|
|
||||||
|
next_row, next_col = coord[0] + diff[0], coord[1] + diff[1]
|
||||||
|
grid[next_row][next_col], grid[coord[0]][coord[1]] = (
|
||||||
|
grid[coord[0]][coord[1]],
|
||||||
|
grid[next_row][next_col],
|
||||||
|
)
|
||||||
|
row, col = next_row, next_col
|
||||||
|
|
||||||
|
case _:
|
||||||
|
raise Exception("unexpected grid value: ", grid[next_row][next_col])
|
||||||
|
# print("\n".join(["".join(row) for row in grid]))
|
||||||
|
|
||||||
|
gps_sum: int = 0
|
||||||
|
for r in range(len(grid)):
|
||||||
|
for c in range(len(grid[0])):
|
||||||
|
if grid[r][c] == "[":
|
||||||
|
gps_sum += r * 100 + c
|
||||||
|
|
||||||
|
return gps_sum
|
||||||
|
|
||||||
|
|
||||||
|
small_example = """########
|
||||||
|
#..O.O.#
|
||||||
|
##@.O..#
|
||||||
|
#...O..#
|
||||||
|
#.#.O..#
|
||||||
|
#...O..#
|
||||||
|
#......#
|
||||||
|
########
|
||||||
|
|
||||||
|
<^^>>>vv<v>>v<<"""
|
||||||
|
|
||||||
|
example = """##########
|
||||||
|
#..O..O.O#
|
||||||
|
#......O.#
|
||||||
|
#.OO..O.O#
|
||||||
|
#..O@..O.#
|
||||||
|
#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^<<^"""
|
||||||
|
|
||||||
|
input = open("input.txt").read().strip()
|
||||||
|
|
||||||
|
print(f"part1 small_example: {part1(small_example)} want 2028")
|
||||||
|
print(f"part1 example: {part1(example)} want 10092")
|
||||||
|
print(f"part1: {part1(input)} want 1413675")
|
||||||
|
|
||||||
|
print(f"part2 example: {part2(example)} want 9021")
|
||||||
|
print(f"part2: {part2(input)} want 1399772")
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
import heapq
|
||||||
|
|
||||||
|
|
||||||
|
diffs: list[tuple[int, int]] = [
|
||||||
|
(0, 1), # start at index 0 facing "east"/right
|
||||||
|
(-1, 0), # up
|
||||||
|
(0, -1), # left
|
||||||
|
(1, 0), # down
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def day16(input: str, part: int) -> int:
|
||||||
|
grid = [list(line) for line in input.splitlines()]
|
||||||
|
start_row: int = 0
|
||||||
|
start_col: int = 0
|
||||||
|
for r in range(len(grid)):
|
||||||
|
for c in range(len(grid[0])):
|
||||||
|
if grid[r][c] == "S":
|
||||||
|
start_row = r
|
||||||
|
start_col = c
|
||||||
|
|
||||||
|
# i think i'd rather write a struct in go... these tuples are easy to make
|
||||||
|
# but annoying to maintain in my head...
|
||||||
|
# score, row, col, dir_index
|
||||||
|
node: tuple[int, int, int, int, set[tuple[int, int]]] = (
|
||||||
|
int(0),
|
||||||
|
start_row,
|
||||||
|
start_col,
|
||||||
|
int(0),
|
||||||
|
set(),
|
||||||
|
)
|
||||||
|
min_heap = [node]
|
||||||
|
heapq.heapify(min_heap)
|
||||||
|
|
||||||
|
coord_to_min_score: dict[tuple[int, int, int], int] = {}
|
||||||
|
|
||||||
|
best_score: int = 1_000_000 # big enough to not interfere with actual input answer
|
||||||
|
final_path_coords: set[tuple[int, int]] = set()
|
||||||
|
|
||||||
|
while len(min_heap) > 0:
|
||||||
|
popped = heapq.heappop(min_heap)
|
||||||
|
|
||||||
|
# part2 exit once best_score is set down to the actual min value
|
||||||
|
if popped[0] > best_score:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# row, col, dir_index
|
||||||
|
coord = (popped[1], popped[2], popped[3])
|
||||||
|
if coord in coord_to_min_score:
|
||||||
|
prev_score = coord_to_min_score[coord]
|
||||||
|
# if previous score at this coord and direction is better then continue
|
||||||
|
if popped[0] > prev_score:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# update best score to reach this coord
|
||||||
|
coord_to_min_score[coord] = popped[0]
|
||||||
|
|
||||||
|
# end reached
|
||||||
|
if grid[popped[1]][popped[2]] == "E":
|
||||||
|
best_score = popped[0]
|
||||||
|
final_path_coords |= popped[4]
|
||||||
|
final_path_coords.add((popped[1], popped[2]))
|
||||||
|
continue
|
||||||
|
|
||||||
|
# in same direction
|
||||||
|
diff = diffs[popped[3]]
|
||||||
|
next = (
|
||||||
|
popped[0] + 1,
|
||||||
|
popped[1] + diff[0],
|
||||||
|
popped[2] + diff[1],
|
||||||
|
popped[3],
|
||||||
|
popped[4] | {(popped[1], popped[2])},
|
||||||
|
)
|
||||||
|
if grid[next[1]][next[2]] in ".E":
|
||||||
|
heapq.heappush(min_heap, next)
|
||||||
|
|
||||||
|
# 90 deg turns
|
||||||
|
heapq.heappush(
|
||||||
|
min_heap,
|
||||||
|
(popped[0] + 1000, popped[1], popped[2], (popped[3] + 1) % 4, popped[4]),
|
||||||
|
)
|
||||||
|
heapq.heappush(
|
||||||
|
min_heap,
|
||||||
|
(popped[0] + 1000, popped[1], popped[2], (popped[3] - 1) % 4, popped[4]),
|
||||||
|
)
|
||||||
|
|
||||||
|
if part == 1:
|
||||||
|
return best_score
|
||||||
|
|
||||||
|
return len(final_path_coords)
|
||||||
|
|
||||||
|
|
||||||
|
example = """###############
|
||||||
|
#.......#....E#
|
||||||
|
#.#.###.#.###.#
|
||||||
|
#.....#.#...#.#
|
||||||
|
#.###.#####.#.#
|
||||||
|
#.#.#.......#.#
|
||||||
|
#.#.#####.###.#
|
||||||
|
#...........#.#
|
||||||
|
###.#.#####.#.#
|
||||||
|
#...#.....#.#.#
|
||||||
|
#.#.#.###.#.#.#
|
||||||
|
#.....#...#.#.#
|
||||||
|
#.###.#.#.#.#.#
|
||||||
|
#S..#.....#...#
|
||||||
|
###############"""
|
||||||
|
|
||||||
|
example2 = """#################
|
||||||
|
#...#...#...#..E#
|
||||||
|
#.#.#.#.#.#.#.#.#
|
||||||
|
#.#.#.#...#...#.#
|
||||||
|
#.#.#.#.###.#.#.#
|
||||||
|
#...#.#.#.....#.#
|
||||||
|
#.#.#.#.#.#####.#
|
||||||
|
#.#...#.#.#.....#
|
||||||
|
#.#.#####.#.###.#
|
||||||
|
#.#.#.......#...#
|
||||||
|
#.#.###.#####.###
|
||||||
|
#.#.#...#.....#.#
|
||||||
|
#.#.#.#####.###.#
|
||||||
|
#.#.#.........#.#
|
||||||
|
#.#.#.#########.#
|
||||||
|
#S#.............#
|
||||||
|
#################"""
|
||||||
|
|
||||||
|
input = open("input.txt").read().strip()
|
||||||
|
|
||||||
|
print(f"day16 example: {day16(example, 1)} want 7036")
|
||||||
|
print(f"day16 example2: {day16(example2,1)} want 11048")
|
||||||
|
print(f"day16 actual: {day16(input,1)} want 83444")
|
||||||
|
|
||||||
|
print(f"part2 example: {day16(example,2)} want 45")
|
||||||
|
print(f"part2 example2: {day16(example2,2)} want 64")
|
||||||
|
print(f"part2 actual: {day16(input,2)} want 483")
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
def part1(input: str) -> str:
|
||||||
|
matches = re.findall(r"\d+", input)
|
||||||
|
|
||||||
|
reg_a: int = int(matches[0])
|
||||||
|
reg_b: int = int(matches[1])
|
||||||
|
reg_c: int = int(matches[2])
|
||||||
|
|
||||||
|
program: list[int] = [int(x) for x in matches[3:]]
|
||||||
|
|
||||||
|
out = run(program, reg_a, reg_b, reg_c)
|
||||||
|
return ",".join([str(v) for v in out])
|
||||||
|
|
||||||
|
|
||||||
|
def run(program: list[int], reg_a: int, reg_b: int, reg_c: int) -> list[int]:
|
||||||
|
def get_combo_operand(combo: int) -> int:
|
||||||
|
if 0 <= combo <= 3:
|
||||||
|
return combo
|
||||||
|
if combo == 4:
|
||||||
|
return reg_a
|
||||||
|
if combo == 5:
|
||||||
|
return reg_b
|
||||||
|
if combo == 6:
|
||||||
|
return reg_c
|
||||||
|
raise Exception("unexpected combo value: ", combo)
|
||||||
|
|
||||||
|
i: int = 0
|
||||||
|
out: list[int] = []
|
||||||
|
while i < len(program):
|
||||||
|
opcode = program[i]
|
||||||
|
operand = program[i + 1]
|
||||||
|
match opcode:
|
||||||
|
case 0:
|
||||||
|
reg_a = reg_a // (2 ** get_combo_operand(operand))
|
||||||
|
i += 2
|
||||||
|
case 1:
|
||||||
|
reg_b = reg_b ^ operand
|
||||||
|
i += 2
|
||||||
|
case 2:
|
||||||
|
reg_b = get_combo_operand(operand) % 8
|
||||||
|
i += 2
|
||||||
|
case 3:
|
||||||
|
if reg_a != 0:
|
||||||
|
i = operand
|
||||||
|
else:
|
||||||
|
i += 2
|
||||||
|
case 4:
|
||||||
|
reg_b = reg_b ^ reg_c
|
||||||
|
i += 2
|
||||||
|
case 5:
|
||||||
|
out.append(get_combo_operand(operand) % 8)
|
||||||
|
i += 2
|
||||||
|
case 6:
|
||||||
|
reg_b = reg_a // (2 ** get_combo_operand(operand))
|
||||||
|
i += 2
|
||||||
|
case 7:
|
||||||
|
reg_c = reg_a // (2 ** get_combo_operand(operand))
|
||||||
|
i += 2
|
||||||
|
case _:
|
||||||
|
raise Exception("unhandled opcode", opcode)
|
||||||
|
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
# only works on actual input
|
||||||
|
def part2() -> int:
|
||||||
|
matches = re.findall(r"\d+", input)
|
||||||
|
program: list[int] = [int(x) for x in matches[3:]]
|
||||||
|
|
||||||
|
# generate each element of the program one at a time, starting at the end
|
||||||
|
output = str
|
||||||
|
reg_as: list[int] = []
|
||||||
|
for i in range(1, 8):
|
||||||
|
output = run_optimized(i)
|
||||||
|
if output == program[-len(output) :]:
|
||||||
|
reg_as.append(i)
|
||||||
|
|
||||||
|
digit_count = 1
|
||||||
|
while digit_count < 16:
|
||||||
|
next_reg_As: list[int] = []
|
||||||
|
for a in reg_as:
|
||||||
|
a *= 8
|
||||||
|
for i in range(8):
|
||||||
|
output = run_optimized(a + i)
|
||||||
|
if output == program[-len(output) :]:
|
||||||
|
next_reg_As.append(a + i)
|
||||||
|
reg_as = next_reg_As
|
||||||
|
digit_count += 1
|
||||||
|
|
||||||
|
# first reg_as will be smallest
|
||||||
|
return reg_as[0]
|
||||||
|
|
||||||
|
|
||||||
|
def run_optimized(a: int) -> list[int]:
|
||||||
|
b: int = 0
|
||||||
|
output: list[int] = []
|
||||||
|
while a != 0:
|
||||||
|
# 2,4, 1,1, 7,5, 4,0, 0,3, 1,6, 5,5, 3,0
|
||||||
|
# pen and paper "algebra"
|
||||||
|
b = ((a % 8) ^ 1) ^ (a // (2 ** ((a % 8) ^ 1))) ^ 6
|
||||||
|
output.append(b % 8)
|
||||||
|
a = a // 8
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
example = """Register A: 729
|
||||||
|
Register B: 0
|
||||||
|
Register C: 0
|
||||||
|
|
||||||
|
Program: 0,1,5,4,3,0"""
|
||||||
|
|
||||||
|
input = open("input.txt").read().strip()
|
||||||
|
|
||||||
|
print(f"part1 example: {part1(example)} want '4,6,3,5,6,3,5,2,1,0'")
|
||||||
|
print(f"part1 actual: {part1(input)} want '1,6,3,6,5,6,5,1,7'")
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"optimized {",".join([str(x) for x in run_optimized(30899381)])} want {part1(input)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
example_part2 = """Register A: 2024
|
||||||
|
Register B: 0
|
||||||
|
Register C: 0
|
||||||
|
|
||||||
|
Program: 0,3,5,4,3,0"""
|
||||||
|
|
||||||
|
# print(f"part2 example: {part1(example_part2, 2)} want 117440")
|
||||||
|
print(f"part2 actual: {part2()} want 247839653009594")
|
||||||
Reference in New Issue
Block a user