Advent of Code 2024, Day 6

For part 2, I thought about sharing information about cyclic states across obstacle positions, but whether a state will end up in a cycle or not depends on the position of the obstacle, so it wouldn’t work.

So I had to brute-force it, and the code was very slow. I read that others had the same problem. But I later read about two optimizations: use complex numbers for coordinates instead of pairs of integers, and only check states for repetition at turns instead of during straight walks. The resulting code was more than 100 times faster.

Part 1 (0.05 seconds)

import sys

def count_visited(lines, i, j):
    diri, dirj = -1, 0
    visited = {(i, j)}
    while True:
        newi, newj = i+diri, j+dirj
        if not 0 <= newi < len(lines): break
        if not 0 <= newj < len(lines[newi]): break
        if lines[newi][newj] == "#":
            diri, dirj = dirj, -diri
        else:
            i, j = newi, newj
            visited.add((i, j))
    return len(visited)

with open(sys.argv[1]) as f:
    lines = f.readlines()
    lines = [line.strip() for line in lines]

for i in range(len(lines)):
    for j in range(len(lines[i])):
        if lines[i][j] == "^":
            starti, startj = i, j
            break

print(count_visited(lines, starti, startj))

Part 2, with arrays (13 minutes)

import sys

def get_trail(lines, i, j, obsi, obsj):
    diri, dirj = -1, 0
    states = [(i, j, diri, dirj)]
    while True:
        newi, newj = i+diri, j+dirj
        if not 0 <= newi < len(lines): break
        if not 0 <= newj < len(lines[newi]): break
        if lines[newi][newj] == "#" or (newi, newj) == (obsi, obsj):
            diri, dirj = dirj, -diri
            states.append((i, j, diri, dirj))
        elif (newi, newj, diri, dirj) in states:
            return []
        else:
            i, j = newi, newj
            states.append((i, j, diri, dirj))
    return states

with open(sys.argv[1]) as f:
    lines = f.readlines()
    lines = [line.strip() for line in lines]

for i in range(len(lines)):
    for j in range(len(lines[i])):
        if lines[i][j] == "^":
            starti, startj = i, j
            break

trail = get_trail(lines, starti, startj, -1, -1)
candidates = set([(i, j) for (i, j, _, _) in trail])
s = 0
x = 0
for (i, j) in candidates:
    x += 1
    print("progress", x / len(candidates))
    s += get_trail(lines, starti, startj, i, j) == []
print(s)

Part 2, with complex numbers (7 seconds)

import sys

with open(sys.argv[1]) as f:
    lines = [line.strip() for line in f]

walls = set()
for i, line in enumerate(lines):
    for j, c in enumerate(line):
        if c == "#":
            walls.add(i + j*1j)
        elif c == "^":
            start = i + j*1j

max_i = len(lines)-1
max_j = len(lines[i])-1
def is_outside(pos):
    return not (0 <= pos.real <= max_i and 0 <= pos.imag <= max_j)

def get_trail():
    pos = start
    facing = -1
    tiles = {pos}
    while True:
        new = pos+facing
        if is_outside(new): break
        if new in walls:
            facing *= -1j
        else:
            pos = new
            tiles.add(pos)
    return tiles

def is_loop(obs):
    pos = start
    facing = -1
    after_turns = {(pos, facing)}
    while True:
        new = pos+facing
        if is_outside(new): return False
        if new == obs or new in walls:
            facing *= -1j
            if (pos, facing) in after_turns: return True
            after_turns.add((pos, facing))
        else:
            pos = new

candidates = get_trail() - {start}
print(sum(is_loop(obs) for obs in candidates))

2024-12-25

Proxy Information
Original URL
gemini://dkalak.de/aoc/06.gmi
Status Code
Success (20)
Meta
text/gemini; lang=en
Capsule Response Time
107.86343 milliseconds
Gemini-to-HTML Time
0.915989 milliseconds

This content has been proxied by September (ba2dc).