summaryrefslogblamecommitdiffstats
path: root/src/day12.exs
blob: 522bb396042617ed768268e84d102348b770a940 (plain) (tree)



































































































































                                                                             
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}")