Day 6
We need to walk a map. I’ll define a State
struct so
that we can use Julia’s Iterator
protocol to walk the
walk.
struct State
::CartesianIndex{2}
pos::Int
dirend
const DIRECTIONS = [
CartesianIndex(0, -1), CartesianIndex(1, 0),
CartesianIndex(0, 1), CartesianIndex(-1, 0)]
position(s::State) = s.pos
direction(s::State) = DIRECTIONS[s.dir]
turn_right(d::Int) = mod1(d+1, 4)
turn_right(s::State) = State(s.pos, turn_right(s.dir))
forward(s::State) = State(s.pos + direction(s), s.dir)
struct Walk
::Matrix{Char}
field::State
start::Union{CartesianIndex,Nothing}
extraend
function read_input(io::IO)
= read(io, String) |> split .|> collect |> stack
field = findfirst((==)('^'), field)
start = '.'
field[start] return Walk(field, State(start, 1), nothing)
end
In this case (unlike Day 01), Julia’s Iterator interface gives us a very compact way to write down our solution.
Base.iterate(walk::Walk) = iterate(walk, walk.start)
function Base.iterate(walk::Walk, s::State)
= forward(s)
next checkbounds(Bool, walk.field, position(next)) && return nothing
!
if walk.field[position(next)] == '#' ||
position(next) == walk.extra
= turn_right(s)
next end
(next, next)end
Base.IteratorSize(::Walk) = Base.SizeUnknown()
Part 1
Part 1 is now a one-liner:
= read_input(io)
walk = Iterators.map(position, walk) |> unique |> length part1
Part 2
In Part 2 we need to find all places to place a box, such that we
create a loop. I tried to be smart about this, but kept failing at that.
The detect_walk
function walks the walk until a state is
repeated.
function detect_loop(walk::Walk, state_set::BitArray{3})
.= 0
state_set for s in walk
= Tuple(position(s))
p ..., s.dir] && return true
state_set[p..., s.dir] = true
state_set[pend
return false
end
Then we try every position along the original path and put a box in front of it. We need to take care that the box doesn’t end up outside the field, on the starting position or on top of another box.
function possible_loop_points(walk::Walk)
= Set{CartesianIndex{2}}()
obstructions = BitArray(undef, size(walk.field)..., 4)
state_set for s in walk
= position(forward(s))
t checkbounds(Bool, walk.field, t) ||
(!== position(walk.start) ||
t == '#') && continue
walk.field[t]
detect_loop(Walk(walk.field, walk.start, t), state_set) &&
push!(obstructions, t)
end
return obstructions
end
This is not very fast,
= walk |> possible_loop_points |> length part2
Main and test
module Day06
<<day06>>
function main(io::IO)
<<day06-part1>>
<<day06-part2>>
return part1, part2
end
end
using AOC2024.Day06: read_input, possible_loop_points
let testmap = """
....#.....
.........#
..........
..#.......
.......#..
..........
.#..^.....
........#.
#.........
......#..."""
= read_input(IOBuffer(testmap))
walk @test walk |> possible_loop_points |> length == 6
end