호두나무 공방/Advent of Code

Advent of Code 2021 Day 25 in Elixir - Sea Cucumber 풀이 (완주!)

2022. 1. 1. 01:47

문제 보기

일정 규칙에 따라 해삼(sea cucumber)이 움직일 때, 이들의 움직임이 언제 멈추는지를 구하는 문제였다. 25일차는 문제가 하나로, 나머지 한 문제는 24일차까지의 모든 문제를 맞추는 것으로 해결한 것으로 보는 것 같다(나는 19일차 한 문제가 부족하다. 후샏...).
규칙을 단순히 코드로 옮기면 되는 것뿐이라 구현에 어려움은 없었다. 얼마나 깔끔하게 만들 수 있느냐가 승부지점인 느낌.

defmodule Day25 do
  def parse_input(file_name) do
    input =
      file_name
      |> File.read!()
      |> String.split("\n")
      |> Enum.map(&String.codepoints/1)

    w = input |> hd() |> length()
    h = input |> length()

    {nested_list_to_map(input), w, h}
  end

  defp nested_list_to_map(nested_list) do
    nested_list
    |> Enum.with_index()
    |> Enum.flat_map(fn {row, row_index} ->
      row
      |> Enum.with_index()
      |> Enum.map(fn {val, col_index} ->
        {{col_index, row_index}, val}
      end)
      |> Enum.reject(fn {_k, v} -> v == "." end)
    end)
    |> Enum.into(%{})
  end

  def run_q1(file_name \\ "input/day25.txt") do
    file_name
    |> parse_input()
    |> do_run_q1(0, false)
    |> print()
    |> IO.puts()
  end

  defp do_run_q1({map, w, h}, count, true) do
    IO.inspect(count)
    {map, w, h}
  end

  defp do_run_q1({map, w, h}, count, false) do
    east_move =
      map
      |> Enum.reduce(%{}, fn
        {{col_index, row_index}, ">" = ch}, acc_map ->
          case next_point_empty?({map, w, h}, {col_index, row_index}, ch) do
            {next_point, ^ch, true} ->
              Map.put(acc_map, next_point, ch)

            {_, ^ch, false} ->
              Map.put(acc_map, {col_index, row_index}, ch)
          end

        {{col_index, row_index}, "v" = ch}, acc_map ->
          Map.put(acc_map, {col_index, row_index}, ch)
      end)

    # print({east_move, w, h})
    # |> IO.puts()

    south_move =
      east_move
      |> Enum.reduce(%{}, fn
        {{col_index, row_index}, "v" = ch}, acc_map ->
          case next_point_empty?({east_move, w, h}, {col_index, row_index}, ch) do
            {next_point, ^ch, true} ->
              Map.put(acc_map, next_point, ch)

            {_, ^ch, false} ->
              Map.put(acc_map, {col_index, row_index}, ch)
          end

        {{col_index, row_index}, ">" = ch}, acc_map ->
          Map.put(acc_map, {col_index, row_index}, ch)
      end)

    do_run_q1({south_move, w, h}, count + 1, map == south_move)
  end

  defp next_point_empty?({map, w, h}, {x, y}, ch) do
    next_point =
      case ch do
        ">" -> if x == w - 1, do: {0, y}, else: {x + 1, y}
        "v" -> if y == h - 1, do: {x, 0}, else: {x, y + 1}
        _ -> nil
      end

    {
      next_point,
      ch,
      not is_nil(next_point) and Map.get(map, next_point, ".") == "."
    }
  end

  defp print({map, w, h}) do
    0..(h - 1)
    |> Enum.map(fn y ->
      0..(w - 1)
      |> Enum.map(fn x ->
        Map.get(map, {x, y}, ".")
      end)
      |> Enum.join("")
    end)
    |> Enum.join("\n")
  end
end

이렇게 AoC2021의 모든 문제를 풀어봤다. 답만 겨우 맞춘 문제도 있고, 먼저 푼 다른 사람들의 코드에서 인사이트를 얻은 경우도 있지만, 한 문제를 제외하고 모두 직접 코드를 작성하여 해결했다는 점에 의의를 두고 싶다. 2년 전에 도전했을 때 중도하차했던 것을 생각하면 꽤 만족스럽다.
여러 다른 일로 스케줄이 꼬이면서 중간부터는 리얼타임으로 달리지 못했지만, 한 달동안 매일 몇 시간씩 시간 내는 것 자체가 쉽지 않은 일이므로 12월 안에 완주한 것 자체로 의미 있는 결과라고 평가하고 싶다(글 올리는 것은 조금 늦어 해를 넘겨버렸지만). 자체축하🎉
후반에 난이도가 상승함과 더불어 마음도 급해지면서 알아보기 쉽지 않은 코드를 꽤 작성한 것 같다. 다음 도전에는 조금 더 나은 실력으로 좋은 코드를 작성할 수 있었으면 좋겠다.

덧. 입력 파싱, 순열이나 조합, 중첩 리스트 처리, 진법 변환, 그림 출력과 같이 자주 쓰이는 기능들은 미리 유틸리티성 함수로 만들어 두면 나중에 여러모로 도움이 될 것 같다. 이상!