Too slow to edit my last post. Once I had the thought of getting the area of an irregular polygon it was just a matter of realizing you have to subtract the nodes on the perimeter from that area. Re-wrote my pt 1 to make it reusable for pt 2.
defmodule Day10 do
@moduledoc """
Day10 AoC Solutions
"""
alias AocToolbox.Input
def input(:test),
do: """
.....
.S-7.
.|.|.
.L-J.
.....
"""
def input(:test2),
do: """
..F7.
.FJ|.
SJ.L7
|F--J
LJ...
"""
def input(:test3),
do: """
...........
.S-------7.
.|F-----7|.
.||.....||.
.||.....||.
.|L-7.F-J|.
.|..|.|..|.
.L--J.L--J.
...........
"""
def input(:test4),
do: """
.F----7F7F7F7F-7....
.|F--7||||||||FJ....
.||.FJ||||||||L7....
FJL7L7LJLJ||LJ.L-7..
L--J.L7...LJS7F-7L7.
....F-J..F7FJ|L7L7L7
....L7.F7||L7|.L7L7|
.....|FJLJ|FJ|F7|.LJ
....FJL-7.||.||||...
....L---J.LJ.LJLJ...
"""
def input(:test5),
do: """
FF7FSF7F7F7F7F7F---7
L|LJ||||||||||||F--J
FL-7LJLJ||||||LJL-77
F--JF--7||LJLJ7F7FJ-
L---JF-JLJ.||-FJLJJ7
|F|F-JF---7F7-L7L|7|
|FFJF7L7F-JF7|JL---7
7-L-JL7||F7|L7F-7F7|
L.L7LFJ|||||FJL7||LJ
L7JLJL-JLJLJL--JLJ.L
"""
def input(:real), do: Input.load(__DIR__ <> "/input.txt")
def solve(1, mode) do
__MODULE__.Part1.solve(input(mode))
end
def solve(2, mode) do
__MODULE__.Part2.solve(input(mode))
end
defmodule Part1 do
@direction %{0 => :north, 1 => :south, 2 => :east, 3 => :west}
def solve(input) do
input
|> parse()
|> find_loop()
|> furthest_distance()
end
def parse(input) do
input
|> Input.lines()
|> Enum.with_index()
|> Enum.flat_map(fn {line, ndx} ->
line
|> String.graphemes()
|> Enum.with_index()
|> Enum.map(fn {char, ndx2} -> {ndx, {ndx2, char}} end)
end)
|> Enum.reduce({:digraph.new(), {}}, fn {k1, {k2, v}}, {g, start} = acc ->
case v do
"." ->
acc
"S" ->
:digraph.add_vertex(g, {k1, k2}, v)
{g, {k1, k2}}
_ ->
:digraph.add_vertex(g, {k1, k2}, v)
{g, start}
end
end)
end
def find_loop({graph, start}) do
start
|> neighbors()
|> Enum.map(&:digraph.vertex(graph, &1))
|> Enum.with_index()
|> Enum.filter(fn {v, _} -> v end)
|> Enum.map(fn {v, i} -> {v, @direction[i]} end)
|> Task.async_stream(fn {v, direction} ->
conn(graph, v, direction, 1, [start])
end)
|> Stream.filter(fn res ->
res != {:ok, {:err, :dead_end}}
end)
|> Enum.at(0)
end
def conn(graph, {coord, label}, incoming_direction, count \\ 1, path \\ []) do
case {label, incoming_direction} do
{"-", :east} -> next_step(graph, east(coord), count + 1, :east, [coord | path])
{"-", :west} -> next_step(graph, west(coord), count + 1, :west, [coord | path])
{"|", :north} -> next_step(graph, north(coord), count + 1, :north, [coord | path])
{"|", :south} -> next_step(graph, south(coord), count + 1, :south, [coord | path])
{"J", :east} -> next_step(graph, north(coord), count + 1, :north, [coord | path])
{"J", :south} -> next_step(graph, west(coord), count + 1, :west, [coord | path])
{"L", :west} -> next_step(graph, north(coord), count + 1, :north, [coord | path])
{"L", :south} -> next_step(graph, east(coord), count + 1, :east, [coord | path])
{"F", :west} -> next_step(graph, south(coord), count + 1, :south, [coord | path])
{"F", :north} -> next_step(graph, east(coord), count + 1, :east, [coord | path])
{"7", :east} -> next_step(graph, south(coord), count + 1, :south, [coord | path])
{"7", :north} -> next_step(graph, west(coord), count + 1, :west, [coord | path])
_ -> {:err, :dead_end}
end
end
def next_step(graph, curr, count, dir, path) do
case {:digraph.vertex(graph, curr), dir} do
{false, _} -> {:err, :dead_end}
{{^curr, "."}, _} -> {:err, :dead_end}
{{^curr, "S"}, _} -> {:ok, count, path}
{{coord, label}, dir} -> conn(graph, {coord, label}, dir, count, path)
end
end
def neighbors({{r, c}, _}), do: neighbors({r, c})
def neighbors(coord) do
[north(coord), south(coord), east(coord), west(coord)]
end
defp north({r, c}), do: {r - 1, c}
defp south({r, c}), do: {r + 1, c}
defp east({r, c}), do: {r, c + 1}
defp west({r, c}), do: {r, c - 1}
defp furthest_distance({_, {_, n, _path}}), do: ceil(n / 2)
end
defmodule Part2 do
@moduledoc """
Do part 1, building the path for the loop. Use the shoelace formula to get the area bounded by the loop.
Subtract the length of the loop to get the number of blocks enclosed by the loop.
"""
def solve(input) do
input
|> parse()
|> Day10.Part1.find_loop()
|> elem(1)
|> elem(2)
|> calc_interior_points()
end
defp calc_interior_points(path) do
perimeter = length(path)
area = AocToolbox.Math.shoelace_formula(path)
area - perimeter / 2 + 1
end
defp parse(input) do
Day10.Part1.parse(input)
end
end
end
#########
defmodule AocToolbox.Math do
defp do_shoelace([[r1 | r_tl] = rows, [c1 | c_tl] = cols]) do
blue = Enum.zip(rows, c_tl ++ [c1]) |> Enum.reduce(0, fn {r, c}, sum -> sum + r * c end)
red = Enum.zip(cols, r_tl ++ [r1]) |> Enum.reduce(0, fn {c, r}, sum -> sum + c * r end)
(abs(blue - red) / 2) |> floor()
end
end
Would have been cool to try it with Nx but now I’m too far behind to keep tinkering.