호두나무 공방/Advent of Code

Advent of Code 2022 Day 9 in Elixir - Rope Bridge 풀이

2022. 12. 11. 19:00

문제 보기

정해진 길이의 로프의 앞 부분을 움직였을 때 끝 부분의 자취를 구하는 문제였다(첫 번째 문제는 짧은 줄, 두 번째 문제는 긴 줄). 플랑크 길이나 온갖 어려운 단어들이 나와서 이게 뭔 소린가 하며 읽었는데, 그런 부분까지 합해서 이번 AoC의 특색 있는 문제 중 하나로 꼽힐 것 같다.

코드 전체적인 구조를 잡는 데 조금 시간이 걸렸다. 로프 끝 부분은 시작 부분과 멀어져야 하는 상황에서만 움직이면 되는 것을 알고, 일단 로프 시작 부분을 움직인 뒤 머리 부분의 방향과 인접 여부를 따져 필요한 경우에만 끝 부분을 움직이도록 구현했다. case문이 좀 많긴 하지만 그래도 제법 깔끔하게 각 경우를 정리하지 않았나 싶다.

defmodule Omicron.Day9 do
  @dir "data/day9/"

  defmodule Trace do
    defstruct head: {0, 0}, tail: {0, 0}, visit: MapSet.new()
  end

  defmodule Trace2 do
    defstruct knots: List.duplicate({0, 0}, 10), visit: MapSet.new()
  end

  def q1(file_name \\ "q.txt") do
    File.read!(@dir <> file_name)
    |> String.split("\n")
    |> Enum.flat_map(fn move ->
      String.split(move, " ")
      |> then(fn [dir, cnt] -> List.duplicate(dir, String.to_integer(cnt)) end)
    end)
    |> Enum.reduce(%Trace{}, fn
      dir, %Trace{head: {x_h, y_h}, tail: {x_t, y_t} = t, visit: v} = acc ->
        new_head =
          case dir do
            "R" -> {x_h + 1, y_h}
            "L" -> {x_h - 1, y_h}
            "D" -> {x_h, y_h - 1}
            "U" -> {x_h, y_h + 1}
          end

        new_tail =
          case relative_pos(new_head, t) do
            {:same, true} -> t
            {_, true} -> t
            {:head_is_right, _} -> {x_t + 1, y_t}
            {:head_is_up_right, _} -> {x_t + 1, y_t + 1}
            {:head_is_down_right, _} -> {x_t + 1, y_t - 1}
            {:head_is_up, _} -> {x_t, y_t + 1}
            {:head_is_down, _} -> {x_t, y_t - 1}
            {:head_is_left, _} -> {x_t - 1, y_t}
            {:head_is_up_left, _} -> {x_t - 1, y_t + 1}
            {:head_is_down_left, _} -> {x_t - 1, y_t - 1}
          end

        %{acc | head: new_head, tail: new_tail, visit: MapSet.put(v, new_tail)}
        |> tap(fn v -> Map.take(v, [:head, :tail]) |> IO.inspect() end)
    end)
    |> Map.get(:visit)
    |> MapSet.size()
  end

  def q2(file_name \\ "q.txt") do
    File.read!(@dir <> file_name)
    |> String.split("\n")
    |> Enum.flat_map(fn move ->
      String.split(move, " ")
      |> then(fn [dir, cnt] -> List.duplicate(dir, String.to_integer(cnt)) end)
    end)
    |> Enum.reduce(%Trace2{}, fn
      dir, %Trace2{knots: [{x_h, y_h} | _] = knots, visit: v} = acc ->
        new_head =
          case dir do
            "R" -> {x_h + 1, y_h}
            "L" -> {x_h - 1, y_h}
            "D" -> {x_h, y_h - 1}
            "U" -> {x_h, y_h + 1}
          end

        new_knots_reversed =
          Enum.reduce(tl(knots), [new_head], fn {x_t, y_t} = t, [h | _] = knot_acc ->
            new_tail =
              case relative_pos(h, t) do
                {:same, true} -> t
                {_, true} -> t
                {:head_is_right, _} -> {x_t + 1, y_t}
                {:head_is_up_right, _} -> {x_t + 1, y_t + 1}
                {:head_is_down_right, _} -> {x_t + 1, y_t - 1}
                {:head_is_up, _} -> {x_t, y_t + 1}
                {:head_is_down, _} -> {x_t, y_t - 1}
                {:head_is_left, _} -> {x_t - 1, y_t}
                {:head_is_up_left, _} -> {x_t - 1, y_t + 1}
                {:head_is_down_left, _} -> {x_t - 1, y_t - 1}
              end

            [new_tail | knot_acc]
          end)

        %{
          acc
          | knots: Enum.reverse(new_knots_reversed),
            visit: MapSet.put(v, hd(new_knots_reversed))
        }
        |> tap(fn v -> Map.take(v, [:knots]) |> IO.inspect() end)
    end)
    |> Map.get(:visit)
    |> MapSet.size()
  end

  defp relative_pos({x_h, y_h} = h, {x_t, y_t} = t) do
    cond do
      h == t -> :same
      x_h > x_t and y_h == y_t -> :head_is_right
      x_h > x_t and y_h > y_t -> :head_is_up_right
      x_h > x_t and y_h < y_t -> :head_is_down_right
      x_h == x_t and y_h > y_t -> :head_is_up
      x_h == x_t and y_h < y_t -> :head_is_down
      x_h < x_t and y_h == y_t -> :head_is_left
      x_h < x_t and y_h > y_t -> :head_is_up_left
      x_h < x_t and y_h < y_t -> :head_is_down_left
      true -> :unknown
    end
    |> then(fn atom ->
      {atom, adjacent?(h, t)}
    end)
  end

  defp adjacent?({x_h, y_h}, {x_t, y_t}) do
    abs(x_t - x_h) <= 1 and abs(y_t - y_h) <= 1
  end
end