호두나무 공방/Advent of Code

Advent of Code 2022 Day 18 in Elixir - Boiling Boulders 풀이

2023. 1. 23. 12:00

문제 보기

어려운 문제가 이어지던 도중에 잠깐 쉬어가는 날이었다. 주어진 좌표에 각각 1x1x1의 정육면체 모양의 용암이 있을 때 전체 표면적(문제 1)과, 용암으로 감싸인 빈 공간(에어 포켓)을 뺀 순수 외부 표면적(문제 2)을 구하는 문제였다.

전체 표면적은 그저 6(단위 정육면체의 표면적)에서 겹치는 면 수만큼 빼면 되니 그리 어렵지 않았고, 에어 포켓을 거꾸로 단위 정육면체의 집합으로 생각하고 표면적을 구하면 순수 외부 표면적도 쉽게 구할 수 있었다. 문제를 풀기에 바빠서 입출력과 로직을 분리할 생각은 안 하고 그냥 q2 함수에서 임시 파일을 만들어 q1 함수를 호출한 것이 옥의 티처럼 보이지만, 지금 수정하기에도 조금 그러니 그대로 올려 둔다.

defmodule Omicron.Day18 do
  @dir "data/day18/"

  def q1(file_name \\ "q.txt") do
    File.read!(@dir <> file_name)
    |> String.split("\n")
    |> Enum.map(fn v ->
      String.split(v, ",")
      |> Enum.map(&String.to_integer/1)
      |> List.to_tuple()
    end)
    |> Enum.reduce({MapSet.new(), 0}, fn {a, b, c} = cube, {cubes, sides} ->
      new_cubes = MapSet.put(cubes, cube)

      overlaps =
        [
          MapSet.member?(cubes, {a - 1, b, c}),
          MapSet.member?(cubes, {a + 1, b, c}),
          MapSet.member?(cubes, {a, b - 1, c}),
          MapSet.member?(cubes, {a, b + 1, c}),
          MapSet.member?(cubes, {a, b, c - 1}),
          MapSet.member?(cubes, {a, b, c + 1})
        ]
        |> Enum.filter(&(&1 == true))
        |> length()

      {new_cubes, sides + 6 - 2 * overlaps}
    end)
  end

  def q2(file_name \\ "q.txt") do
    cubes =
      File.read!(@dir <> file_name)
      |> String.split("\n")
      |> Enum.map(fn v ->
        String.split(v, ",")
        |> Enum.map(&String.to_integer/1)
        |> List.to_tuple()
      end)

    max_x = cubes |> Enum.map(&elem(&1, 0)) |> Enum.max() |> IO.inspect(label: "max_x")
    min_x = cubes |> Enum.map(&elem(&1, 0)) |> Enum.min() |> IO.inspect(label: "min_x")
    max_y = cubes |> Enum.map(&elem(&1, 1)) |> Enum.max() |> IO.inspect(label: "max_y")
    min_y = cubes |> Enum.map(&elem(&1, 1)) |> Enum.min() |> IO.inspect(label: "min_y")
    max_z = cubes |> Enum.map(&elem(&1, 2)) |> Enum.max() |> IO.inspect(label: "max_z")
    min_z = cubes |> Enum.map(&elem(&1, 2)) |> Enum.min() |> IO.inspect(label: "min_z")

    air_pockets =
      min_x..max_x
      |> Enum.flat_map(fn x ->
        min_y..max_y
        |> Enum.flat_map(fn y ->
          min_z..max_z
          |> Enum.filter(fn z -> air_pocket?(cubes, {x, y, z}) end)
          |> Enum.map(fn z -> {x, y, z} end)
        end)
      end)

    air_pocket_input =
      air_pockets
      |> Enum.map(fn {x, y, z} -> "#{x},#{y},#{z}" end)
      |> Enum.join("\n")

    tmp_file_name = "tmp.txt"
    File.write!(@dir <> tmp_file_name, air_pocket_input)

    {_, total_sides} = q1(file_name)
    {_, inner_sides} = q1(tmp_file_name)

    total_sides - inner_sides
  end

  defp air_pocket?(cubes, {x, y, z}) do
    if {x, y, z} in cubes do
      false
    else
      if Enum.any?(cubes, fn {cx, cy, cz} -> cx > x and cy == y and cz == z end) and
           Enum.any?(cubes, fn {cx, cy, cz} -> cx < x and cy == y and cz == z end) and
           Enum.any?(cubes, fn {cx, cy, cz} -> cx == x and cy > y and cz == z end) and
           Enum.any?(cubes, fn {cx, cy, cz} -> cx == x and cy < y and cz == z end) and
           Enum.any?(cubes, fn {cx, cy, cz} -> cx == x and cy == y and cz > z end) and
           Enum.any?(cubes, fn {cx, cy, cz} -> cx == x and cy == y and cz < z end) do
        true
      else
        false
      end
    end
  end
end