defmodule Day12 do
def part1(data) do
grid = data
|> String.split("\n", trim: true)
|> Enum.map(&String.codepoints/1)
grid = for {l, i} <- Enum.with_index(grid),
{c, j} <- Enum.with_index(l) do
{{i, j}, c}
end
|> Map.new()
for pos <- Map.keys(grid), reduce: {MapSet.new(), 0} do
{vis, res} -> solve1(vis, grid, pos)
|> then(fn {vis, a, p} -> {vis, res + a*p} end)
end
|> elem(1)
end
defp solve1(vis, grid, pos) do
if MapSet.member?(vis, pos) do
{vis, 0, 0}
else
for npos <- dirs(pos), reduce: {MapSet.put(vis, pos), 1, 0} do
{vis, a, p} ->
case Map.fetch(grid, npos) do
:error ->
{vis, a, p+1}
{_, v} ->
if v == Map.fetch!(grid, pos) do
{vis, na, np} = solve1(vis, grid, npos)
{vis, a+na, p+np}
else
{vis, a, p+1}
end
end
end
end
end
defp dirs({i, j}) do
[{i+1, j}, {i-1, j}, {i, j+1}, {i, j-1}]
end
def part2(data) do
grid = data
|> String.split("\n", trim: true)
|> Enum.map(&String.codepoints/1)
grid = for {l, i} <- Enum.with_index(grid),
{c, j} <- Enum.with_index(l) do
{{i, j}, c}
end
|> Map.new()
for pos <- Map.keys(grid), reduce: {MapSet.new(), 0} do
{vis, res} -> solve2(vis, grid, pos)
|> then(fn {vis, a, walls} -> {vis, res + a*perim(walls)} end)
end
|> elem(1)
end
defp solve2(vis, grid, pos) do
if MapSet.member?(vis, pos) do
{vis, 0, []}
else
for {npos, dir} <- dirs2(pos), reduce: {MapSet.put(vis, pos), 1, []} do
{vis, a, walls} ->
case Map.fetch(grid, npos) do
:error ->
{vis, a, [{dir, npos} | walls]}
{_, v} ->
if v == Map.fetch!(grid, pos) do
{vis, na, nwalls} = solve2(vis, grid, npos)
{vis, a+na, walls ++ nwalls}
else
{vis, a, [{dir, npos} | walls]}
end
end
end
end
end
defp dirs2({i, j}) do
[{{i+1, j}, :down},
{{i-1, j}, :up},
{{i, j+1}, :right},
{{i, j-1}, :left}]
end
defp dist({a, b}, {c, d}), do: abs(a-c) + abs(b-d)
defp dist(_, nil), do: nil
defp dist(nil, _), do: nil
defp perim([]), do: 0
defp perim(walls) do
gwalls = walls
|> Enum.group_by(&elem(&1,0), &elem(&1,1))
for {dir, walls} <- gwalls do
swalls =
cond do
dir == :up or dir == :down ->
Enum.sort(walls)
true ->
Enum.sort_by(walls, fn {i, j} -> {j, i} end)
end
for w <- swalls, reduce: {nil, 0} do
{prev, acc} ->
if dist(w, prev) == 1 do
{w, acc}
else
{w, acc+1}
end
end
|> elem(1)
end
|> Enum.sum()
end
end
data = IO.read(:stdio, :eof)
{time1 , ans1} = :timer.tc(fn -> Day12.part1(data) end)
IO.puts("Time : #{time1 / 1000000}")
IO.puts("Answer: #{ans1}")
{time2 , ans2} = :timer.tc(fn -> Day12.part2(data) end)
IO.puts("Time : #{time2 / 1000000}")
IO.puts("Answer: #{ans2}")