advent_of_code/elixir/lib/Y2023/day10.ex
2024-11-15 21:03:49 -05:00

229 lines
5.1 KiB
Elixir

defmodule PipeModule do
@pipe_types %{
"|" => {:n, :s},
"-" => {:e, :w},
"L" => {:n, :e},
"J" => {:n, :w},
"7" => {:s, :w},
"F" => {:s, :e},
"." => :ground,
"S" => :animal
}
def parse(input) do
for u <-
input
|> String.split("\n")
|> Stream.with_index()
|> Enum.flat_map(fn {line, line_idx} ->
line
|> String.graphemes()
|> Stream.with_index()
|> Enum.map(fn {pipe_char, index} ->
{{index, line_idx}, Map.get(@pipe_types, pipe_char)}
end)
end),
into: %{} do
u
end
end
def connects?(pipe1, pipe2, _) when pipe1 == :ground or pipe2 == :ground do
false
end
def connects?(pipe1, pipe2, orientation) do
can_connect?(pipe1, orientation) and can_connect?(pipe2, flip(orientation))
end
def flip(orientation) do
case orientation do
:n -> :s
:s -> :n
:w -> :e
:e -> :w
end
end
def can_connect?(:animal, _) do
true
end
def can_connect?({d1, d2}, orientation) do
d1 == orientation or d2 == orientation
end
def to_orientation({x1, x2}, {y1, y2}) do
{z1, z2} = {y1 - x1, y2 - x2}
case z1 do
0 ->
case z2 do
-1 -> :n
1 -> :s
end
1 ->
case z2 do
0 -> :e
end
-1 ->
case z2 do
0 -> :w
end
end
end
def furthest_from_animal(loop) do
loop
|> Enum.reduce(
%{},
fn {pipe, steps}, acc ->
Map.update(acc, pipe, [steps], fn p -> [steps | p] end)
end
)
|> Stream.filter(fn {_, a} -> a |> Enum.count() == 2 end)
|> Enum.reject(fn {_, [a, b]} -> a != b or a == 0 end)
|> Enum.at(0)
end
def find_animal(grid) do
grid
|> Enum.find(nil, fn {_, pipe} -> pipe == :animal end)
|> elem(0)
end
def find_loop({i, j}, pipes, grid) do
pipe = Map.get(grid, {i, j})
connected_neighbors =
for ii <- -1..1,
jj <- -1..1,
(ii == 0 or jj == 0) and !(ii == 0 and jj == 0),
{z1, z2} = {i + ii, j + jj},
z1 != -1 and z2 != -1,
neighbor = Map.get(grid, {z1, z2}),
connects?(pipe, neighbor, to_orientation({i, j}, {z1, z2})) do
{z1, z2}
end
connected_pipes_checked =
Enum.filter(
connected_neighbors,
fn n -> Enum.any?(pipes, fn {p, _} -> n == p end) end
)
if connected_pipes_checked
|> Enum.count() == 2 do
pipes
else
step = pipes |> Enum.count()
Enum.flat_map(
connected_neighbors
|> Enum.reject(fn n -> Enum.any?(pipes, fn {p, _} -> p == n end) end),
fn {ii, jj} ->
find_loop({ii, jj}, [{{i, j}, step} | pipes], grid)
end
)
end
end
defp loop_as_set(loop) do
loop |> Stream.map(&elem(&1, 0)) |> MapSet.new()
end
def to_string(pipe) do
case pipe do
:ground -> "."
:animal -> "S"
# List.to_string([])
{:n, :s} -> <<0x2551::8>>
{:e, :w} -> List.to_string([0x2550])
{:n, :e} -> List.to_string([0x255A])
{:n, :w} -> List.to_string([0x255D])
{:s, :w} -> List.to_string([0x2557])
{:s, :e} -> List.to_string([0x2554])
_ -> ""
end
end
def get_tile_under_animal(loop) do
# TODO
end
def get_inside_count(grid, loop) do
loop_set = loop_as_set(loop)
for room <-
grid
|> Enum.sort_by(fn {{x, y}, _} -> {y, x} end)
|> Enum.map_reduce(
%{n: false, s: false, o: 0, i: 0},
fn {{x, y}, tile}, %{n: n, s: s, o: o, i: i} ->
tile =
if tile == :animal do
get_tile_under_animal(loop)
tile
else
tile
end
{n, s, o, i} =
if loop_set |> MapSet.member?({x, y}) do
n = if PipeModule.opens_north?(tile), do: !n, else: n
s = if PipeModule.opens_south?(tile), do: !s, else: s
{n, s, o, i}
else
{i, o} =
if n || s do
i = i + 1
{i, o}
else
o = o + 1
{i, o}
end
{n, s, o, i}
end
tile_location =
if loop_set |> MapSet.member?({x, y}),
do: :loop,
else:
(case n || s do
true -> :inside
false -> :outside
end)
{{{x, y}, {tile, tile_location}}, %{n: n, s: s, o: o, i: i}}
end
)
|> elem(0),
into: %{} do
room
end
|> Map.values()
|> Enum.filter(&(elem(&1, 1) == :inside))
|> Enum.count()
end
def opens_north?({a, b}) when a == :n or b == :n do
true
end
def opens_north?(_) do
false
end
def opens_south?({a, b}) when a == :s or b == :s do
true
end
def opens_south?(:animal) do
true
end
def opens_south?(_) do
false
end
end