호두나무 공방/Advent of Code

Advent of Code 2021 Day 5 in Elixir - Hydrothermal Venture

2021. 12. 6. 21:45

문제 보기

다른 일정 탓에 조금 늦게 올리는 5일차 문제 풀이. 좌표평면상의 가로, 세로, 대각선(문제 2에서만)상에 있는 열수분출공(hydrothermal vent)에 대해, 2개 이상의 열수맥(?)이 겹치는 곳의 개수를 찾는 문제였다. 지금까지 거의 모든 문제에서 Enum.reduce를 사용했는데, 이번에도 그 힘으로 어렵지 않게 풀 수 있었다.

신경쓴 부분이라고 하면 get_points의 패턴 매칭 부분. 문제 1에서는 가로세로만 고려하면 되어서, get_line 함수 없이 get_points를 4개 함수로 나누었었다.

  • when x1 > x2 (즉 y1 == y2)
  • when x1 < x2 (즉 y1 == y2)
  • when y1 > y2 (즉 x1 == x2)
  • when y1 < y2 (즉 x1 == x2)

문제 1은 그렇게 풀었는데, 문제 2에서 대각선도 생각해야 해서 한 좌표축의 연속된 점들을 리스트로 반환하는 get_line을 분리하고, 여기서 대소관계에 따른 처리를 모두 담당하도록 해서 get_points를 조금 더 단순하게 다듬을 수 있었다. 두 번째 파라미터인 :q1, :q2 아톰은 1번 문제에서 대각선 입력을 처리하지 않아야 해서 임의로 구분한 부분이다.

defmodule Day5 do
  def parse_input do
    path = "input/day5.txt"

    File.read!(path)
    |> String.split("\n")
    |> Enum.map(&parse_vent/1)
  end

  defp parse_vent(input_line) do
    input_line
    |> String.split(" -> ")
    |> Enum.map(fn point ->
      point
      |> String.split(",")
      |> Enum.map(&String.to_integer/1)
      |> List.to_tuple()
    end)
    |> List.to_tuple()
  end

  @spec run_q1 :: non_neg_integer
  def run_q1 do
    parse_input()
    |> Enum.reduce(%{}, fn vent, acc ->
      get_points(vent, :q1)
      |> Enum.reduce(acc, fn point, tmp_acc ->
        Map.update(tmp_acc, point, 1, &(&1 + 1))
      end)
    end)
    |> Enum.filter(fn {_k, v} -> v >= 2 end)
    |> Enum.count()
  end

  @spec run_q2 :: non_neg_integer
  def run_q2 do
    parse_input()
    |> Enum.reduce(%{}, fn vent, acc ->
      get_points(vent, :q2)
      |> Enum.reduce(acc, fn point, tmp_acc ->
        Map.update(tmp_acc, point, 1, &(&1 + 1))
      end)
    end)
    |> Enum.filter(fn {_k, v} -> v >= 2 end)
    |> Enum.count()
  end

  @spec get_points({{integer, integer}, {integer, integer}}, :q1 | :q2) ::
          list({integer, integer})
  def get_points({{x1, y}, {x2, y}}, _) do
    get_line(x1, x2) |> Enum.map(&{&1, y})
  end

  def get_points({{x, y1}, {x, y2}}, _) do
    get_line(y1, y2) |> Enum.map(&{x, &1})
  end

  def get_points({{_x1, _y1}, {_x2, _y2}}, :q1) do
    []
  end

  def get_points({{x1, y1}, {x2, y2}}, :q2) do
    Enum.zip(get_line(x1, x2), get_line(y1, y2))
  end

  @spec get_line(integer, integer) :: list
  def get_line(a, b) when a < b, do: a..b |> Enum.to_list()

  def get_line(a, b) when a > b, do: a..b//-1 |> Enum.to_list()
end