day_23.jl (4226B)
1 #!/usr/bin/env julia 2 # https://adventofcode.com/2022/day/23 3 using AdventOfCode 4 5 example = readlines(IOBuffer(""" 6 ....#.. 7 ..###.# 8 #...#.# 9 .#...## 10 #.###.. 11 ##.#.## 12 .#..#..""")) 13 input = readlines("data/day_23.txt") 14 15 Elf = Tuple{Int, Int} 16 ElfSet = AbstractSet{Elf} 17 18 """ 19 Read the input and return a set of elf positions. 20 """ 21 function parse_input(input::Vector{<:AbstractString}) 22 elves = Set{Elf}() 23 for (y, line) = enumerate(input) 24 for (x, c) = enumerate(split(line, "")) 25 c == "#" && push!(elves, (y, x)) 26 end 27 end 28 elves 29 end 30 31 """ 32 Find the neighbors of an elf. 33 34 Returns a 3x3 boolean matrix that is true IFF an elf is in a position relative 35 to the given elf. 36 """ 37 function neighbors(elf::Elf, elves::ElfSet) 38 neighbors = zeros(Bool, (3, 3)) 39 for Δy = -1:1, Δx = -1:1 40 if (Δy, Δx) == (0, 0) 41 neighbors[2+Δy, 2+Δx] = false # simplify move calculation 42 else 43 neighbors[2+Δy, 2+Δx] = (elf[1]+Δy, elf[2]+Δx) ∈ elves 44 end 45 end 46 neighbors 47 end 48 49 """ 50 Propose a move for one elf at the given timestep. 51 """ 52 function propose_move(elf::Elf, elves::ElfSet, timestep::Int) 53 elfneighbs = neighbors(elf, elves) 54 if !any(elfneighbs) 55 return elf 56 else 57 for i = 0:3 58 direction = [:n, :s, :w, :e][(i + timestep) % 4 + 1] 59 if direction == :n 60 !any(elfneighbs[1, :]) && return (elf[1]-1, elf[2]) 61 elseif direction == :s 62 !any(elfneighbs[3, :]) && return (elf[1]+1, elf[2]) 63 elseif direction == :w 64 !any(elfneighbs[:, 1]) && return (elf[1], elf[2]-1) 65 else # :e 66 !any(elfneighbs[:, 3]) && return (elf[1], elf[2]+1) 67 end 68 end 69 end 70 elf 71 end 72 73 """ 74 Find a mapping from proposed elf positions to current elf positions. 75 76 timestep starts at 0 and should increment at each step in the process. This is 77 a bit confusing (for me anyway), because the mapping is from *proposed* 78 locations to *originating* locations, when the converse seems a bit more 79 intuitive. 80 """ 81 function moveelves(elves::ElfSet, timestep::Int) 82 camefrom = Dict{Elf,Elf}() 83 invalidmoves = Vector{Elf}() 84 # Step 1: Propose moves for each elf, mark invalid moves 85 for elf₁ = elves 86 elf₂ = propose_move(elf₁, elves, timestep) 87 if elf₂ ∈ keys(camefrom) 88 # This is an invalid move. We'll mark it for now and leave the 89 # current elf in its present location 90 camefrom[elf₁] = elf₁ 91 push!(invalidmoves, elf₂) 92 else 93 # This move is fine (so far) 94 camefrom[elf₂] = elf₁ 95 end 96 end 97 98 # Step 2: Put elves at marked-invalid locations back where they came from 99 for elf₂ = invalidmoves 100 camefrom[camefrom[elf₂]] = camefrom[elf₂] 101 delete!(camefrom, elf₂) 102 end 103 104 camefrom 105 end 106 107 moveelves(elves::Dict{Elf,Elf}, timestep::Int) = moveelves(keys(elves), timestep) 108 109 """ 110 Return the (height, width) of the area containing all elves. 111 """ 112 function mapsize(elves::ElfSet) 113 (ymin, ymax) = extrema(x -> x[1], elves) 114 (xmin, xmax) = extrema(x -> x[2], elves) 115 (ymax-ymin+1, xmax-xmin+1) 116 end 117 118 """ 119 Return the number of empty spots on the map. 120 """ 121 function emptyspots(elves::ElfSet) 122 prod(mapsize(elves)) - length(elves) 123 end 124 125 emptyspots(elves::Dict{Elf,Elf}) = emptyspots(keys(elves)) 126 127 function part_1(input) 128 elves = parse_input(input) 129 for ts = 0:9 130 elves = moveelves(elves, ts) 131 end 132 emptyspots(elves) 133 end 134 @assert part_1(example) == 110 135 @info part_1(input) 136 137 function Base.:(==)(elves₁::Dict{Elf,Elf}, elves₂::ElfSet) 138 keys(elves₁) == elves₂ 139 end 140 141 function Base.:(==)(elves₁::ElfSet, elves₂::Dict{Elf,Elf}) 142 elves₁ == keys(elves₂) 143 end 144 145 function Base.:(==)(elves₁::Dict{Elf,Elf}, elves₂::Dict{Elf,Elf}) 146 keys(elves₁) == keys(elves₂) 147 end 148 149 function part_2(input) 150 elves₁ = parse_input(input) 151 for ts = 0:1_000_000 152 elves₂ = moveelves(elves₁, ts) 153 elves₁ == elves₂ && return ts+1 154 elves₁ = elves₂ 155 end 156 nothing 157 end 158 @assert part_2(example) == 20 159 @info part_2(input)