115 lines
3 KiB
Elixir
115 lines
3 KiB
Elixir
|
defmodule AOC.Day14 do
|
||
|
use AOC.Day, day: 14
|
||
|
|
||
|
@compile {:parse_transform, :ms_transform}
|
||
|
|
||
|
def parse_input(lines) do
|
||
|
dish_table = :ets.new(:dish_table, [])
|
||
|
|
||
|
lines
|
||
|
|> Enum.with_index()
|
||
|
|> Enum.each(fn {line, y} ->
|
||
|
line
|
||
|
|> String.to_charlist()
|
||
|
|> Enum.with_index()
|
||
|
|> Enum.each(fn
|
||
|
{?O, x} -> :ets.insert(dish_table, {{y, x}, :round})
|
||
|
{?#, x} -> :ets.insert(dish_table, {{y, x}, :cube})
|
||
|
{?., _x} -> :ok
|
||
|
end)
|
||
|
end)
|
||
|
|
||
|
max_y =
|
||
|
:ets.tab2list(dish_table)
|
||
|
|> Enum.map(&elem(&1, 0))
|
||
|
|> Enum.map(&elem(&1, 0))
|
||
|
|> Enum.max()
|
||
|
|
||
|
max_x =
|
||
|
:ets.tab2list(dish_table)
|
||
|
|> Enum.map(&elem(&1, 0))
|
||
|
|> Enum.map(&elem(&1, 1))
|
||
|
|> Enum.max()
|
||
|
|
||
|
{dish_table, {max_y, max_x}}
|
||
|
end
|
||
|
|
||
|
def get_round_rock_coords(dish_table) do
|
||
|
:ets.select(dish_table, :ets.fun2ms(fn {coords, type} when type == :round -> coords end))
|
||
|
end
|
||
|
|
||
|
def perform_spin_cycle(dish_table, max_coords) do
|
||
|
slide(dish_table, max_coords, fn {y1, _}, {y2, _} -> y1 <= y2 end, fn {y, x} ->
|
||
|
{y - 1, x}
|
||
|
end)
|
||
|
|
||
|
slide(dish_table, max_coords, fn {_, x1}, {_, x2} -> x1 <= x2 end, fn {y, x} ->
|
||
|
{y, x - 1}
|
||
|
end)
|
||
|
|
||
|
slide(dish_table, max_coords, fn {y1, _}, {y2, _} -> y1 >= y2 end, fn {y, x} ->
|
||
|
{y + 1, x}
|
||
|
end)
|
||
|
|
||
|
slide(dish_table, max_coords, fn {_, x1}, {_, x2} -> x1 >= x2 end, fn {y, x} ->
|
||
|
{y, x + 1}
|
||
|
end)
|
||
|
end
|
||
|
|
||
|
def slide(dish_table, max_coords, sort_fun, slide_fun) do
|
||
|
get_round_rock_coords(dish_table)
|
||
|
|> Enum.sort(sort_fun)
|
||
|
|> Enum.each(&slide_rock(dish_table, max_coords, slide_fun, &1))
|
||
|
end
|
||
|
|
||
|
def slide_rock(dish_table, {max_y, max_x} = max_coords, slide_fun, coords) do
|
||
|
{slide_y, slide_x} = slide_coords = slide_fun.(coords)
|
||
|
|
||
|
if not (slide_y < 0 or slide_x < 0 or slide_y > max_y or slide_x > max_x) do
|
||
|
case :ets.lookup(dish_table, slide_coords) do
|
||
|
[] ->
|
||
|
:ets.delete(dish_table, coords)
|
||
|
:ets.insert(dish_table, {slide_coords, :round})
|
||
|
slide_rock(dish_table, max_coords, slide_fun, slide_coords)
|
||
|
|
||
|
_ ->
|
||
|
:ok
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def calculate_total_load(dish_table, {max_y, _}) do
|
||
|
get_round_rock_coords(dish_table)
|
||
|
|> Enum.map(&elem(&1, 0))
|
||
|
|> Enum.map(&(max_y + 1 - &1))
|
||
|
|> Enum.sum()
|
||
|
end
|
||
|
|
||
|
def part1({dish_table, max_coords}) do
|
||
|
slide(dish_table, max_coords, fn {y1, _}, {y2, _} -> y1 <= y2 end, fn {y, x} ->
|
||
|
{y - 1, x}
|
||
|
end)
|
||
|
|
||
|
calculate_total_load(dish_table, max_coords)
|
||
|
end
|
||
|
|
||
|
def part2({dish_table, max_coords}) do
|
||
|
memo_table = :ets.new(:memo_table, [])
|
||
|
|
||
|
# Adjust according to calculations
|
||
|
cycle_count = 109
|
||
|
|
||
|
Enum.each(1..cycle_count, fn i ->
|
||
|
perform_spin_cycle(dish_table, max_coords)
|
||
|
memo = get_round_rock_coords(dish_table) |> Enum.sort()
|
||
|
|
||
|
case :ets.lookup(memo_table, memo) do
|
||
|
[] -> :ets.insert(memo_table, {memo, i})
|
||
|
[{_, i_prev}] -> IO.puts("State already observed: #{i_prev} & #{i}")
|
||
|
end
|
||
|
end)
|
||
|
|
||
|
calculate_total_load(dish_table, max_coords)
|
||
|
end
|
||
|
end
|