igorb
Advent of Code 2023 - Day 16
For Part 1, I was lazy and didn’t want to maintain variables and pass them down to each function (which would also involve merging two different histories after each split), so I used the process dictionary trick I learned about in earlier days from @Aetherus to represent state and have the functions produce side effects (which I thought was ugly). For Part 2, though, I was quite happy with this approach because then I just wrapped this code in Task.async_stream/2 and found the max ![]()
Most Liked
woojiahao
I believe it’s possible to memoize the paths given (row, column, direction). Since you’re just choosing a different starting position, but if the beam traveling left reaches \, it should still be the same path throughout. Though, I did not try this out so it could be false
rugyoga
Pretty straightforward.
A MapSet to prevent loops and brute force.
import AOC
aoc 2023, 16 do
def p1(input) do
input
|> Grid.parse()
|> then(fn {max, grid} -> {max, Map.new(grid)} end)
|> compute({{0, 0}, :east})
end
def compute(grid, start) do
grid
|> recurse([start], MapSet.new(), MapSet.new())
|> Enum.count()
end
def p2(input) do
{{rows, cols}, points} = input |> Grid.parse()
grid = {{rows, cols}, Map.new(points)}
(for row <- 0..(rows-1), do: [{{row, 0}, :east}, {{row, cols-1}, :west}] ++
for col <- 0..(cols-1), do: [{{0, col}, :south}, {{rows-1, col}, :north}])
|> List.flatten()
|> Enum.map(&compute(grid, &1))
|> Enum.max()
end
def recurse(_, [], energized, _), do: energized
def recurse({_, map} = grid, [active | actives], energized, seen) do
{active_pos, active_dir} = active
if not MapSet.member?(seen, active) and Grid.in?(grid, active_pos) do
energized_new = MapSet.put(energized, active_pos)
seen_new = MapSet.put(seen, active)
f = fn candidates -> candidates |> Kernel.++(actives) |> then(&recurse(grid, &1, energized_new, seen_new)) end
item = map[active_pos]
cond do
item == "." or
(item == "|" and active_dir in [:north, :south]) or
(item == "-" and active_dir in [:east, :west]) -> f.([next(active)])
item == "|" -> f.([next({active_pos, :north}), next({active_pos, :south})])
item == "-" -> f.([next({active_pos, :west}), next({active_pos, :east})])
item == "/" -> f.([next({active_pos, flip_sw(active_dir)})])
item == "\\" -> f.([next({active_pos, flip_se(active_dir)})])
end
else
recurse(grid, actives, energized, seen)
end
end
def flip_sw(dir), do: %{north: :east, south: :west, west: :south, east: :north}[dir]
def flip_se(dir), do: %{north: :west, south: :east, west: :north, east: :south}[dir]
def next({{row, col}, :west}), do: {{row, col-1}, :west}
def next({{row, col}, :east}), do: {{row, col+1}, :east}
def next({{row, col}, :north}), do: {{row-1, col}, :north}
def next({{row, col}, :south}), do: {{row+1, col}, :south}
end
woojiahao
TIL of ~w()a to generate a list of atoms instead of words, pretty cool!
lud
Hello,
I was afraid that the second part would ask to rotate mirrors to create the maximum energy but thanks it was much easier.
I have no idea how to optimize that so I just simulate all the beams for part 2, and it takes more than one second. Any idea?
lud
Ah yes I generally do not use task async stream because I like to debug the outputs but now that it works, i’ll try it ![]()
Edit: yeah, 500ms, good enough, thanks!
Also ordered: false comes in handy ![]()







