5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ElixirAdvent Calendar 2024

Day 7

Advent of code 2024 Day 15 Part 2 を Livebook で楽しむ

Last updated at Posted at 2024-12-19

はじめに

Advent of code 2024 Day 15 の Part 2 を解きます

問題文はこちら

実装したノートブックはこちら

Part 1 はこちら

セットアップ

Kino AOC をインストールします

Mix.install([
  {:kino_aoc, "~> 0.1"}
])

Kino AOC の使い方はこちらを参照

入力の取得

"Advent of Code Helper" スマートセルを追加し、 Day 15 の入力を取得します

スクリーンショット 2024-12-19 16.00.15.png

私の答え

私の答えです。
折りたたんでおきます。
▶を押して開いてください。

回答

入力例で考えます
(Part 1 とは違う例です)

small_sample_input =
  """
  #######
  #...#.#
  #.....#
  #..OO@#
  #..O..#
  #.....#
  #######
  
  <vv<<^^<<^^
  """
  |> String.trim()

問題文に沿って、地図の幅を倍にします

spread_map = fn input ->
  input
  |> String.replace("#", "##")
  |> String.replace("O", "[]")
  |> String.replace(".", "..")
  |> String.replace("@", "@.")
end

その上で、地図と移動方向配列として読み込みます

parse_map = fn input ->
  [map_rows, direction_rows] = String.split(input, "\n\n")

  map =
    map_rows
    |> String.split("\n")
    |> Enum.with_index()
    |> Enum.flat_map(fn {row, row_index} ->
      row
      |> String.codepoints()
      |> Enum.with_index()
      |> Enum.map(fn {mark, col_index} ->
        {{col_index, row_index}, mark}
      end)
    end)
    |> Enum.into(%{})

  directions =
    direction_rows
    |> String.replace("\n", "")
    |> String.codepoints()

  {map, directions}
end
{map, directions} =
  small_sample_input
  |> spread_map.()
  |> parse_map.()

実行結果

{%{
   {1, 3} => "#",
   {1, 1} => "#",
   {1, 5} => "#",
   {...} => "#",
   ...
 }, ["<", "v", "v", "<", "<", "^", "^", "<", "<", "^", "^"]}

ロボットの初期位置を取得します

get_robot_point = fn map ->
  map
  |> Enum.find(fn {_, mark} -> mark == "@" end)
  |> elem(0)
end
robot_point = get_robot_point.(map)

実行結果

{10, 3}

Part 1 と基本的な考え方は同じですが、箱の幅がロボットの倍なので、箱が左右にはみ出し、隣の列の箱も巻き込んで進みます

動けるかどうかの判断は、移動しようとしている箱("[" または "]")の先に壁("#")があるか、で行います

例えば、以下の例では上に進むことができません

####
..[]
.[].
..[]
...@
####

逆に、以下のような配置では1回上に進めます

####
..[]
[]..
.[].
..@.
####

実装したモジュールは以下のものです

defmodule Robot do
  def move({map, robot_point}, direction) do
    moving_points = search_end_point(robot_point, direction, map, [])

    if Enum.any?(hd(moving_points), &(elem(&1, 1) == "#")) do
      {map, robot_point}
    else
      {
        go_next(robot_point, direction, moving_points, map),
        get_next_point(robot_point, direction)
      }
    end
  end

  defp go_next(robot_point, direction, moving_points, map) do
    current_points_group = Enum.reverse(moving_points)
    next_points_group = (tl(moving_points) ++ [[{robot_point, "@"}]]) |> Enum.reverse()

    current_points_group
    |> Enum.zip(next_points_group)
    |> Enum.reduce(map, fn {current_points, next_points}, acc_map ->
      current_points
      |> Enum.reduce(acc_map, fn current_point, acc_acc_map ->
        {{cx, _}, _} = current_point

        next_point =
          Enum.find(next_points, fn {{nx, _}, _} ->
            if Enum.member?(["^", "v"], direction) do
              cx == nx
            else
              true
            end
          end)

        mark =
          case next_point do
            nil -> "."
            _ -> elem(next_point, 1)
          end

        Map.put(acc_acc_map, elem(current_point, 0), mark)
      end)
    end)
    |> Map.put(robot_point, ".")
  end

  def search_end_point({rx, ry} = robot_point, direction, map, acc) do
    head =
      case acc do
        [] -> [{robot_point, "@"}]
        _ -> hd(acc)
      end

    next_points =
      head
      |> Enum.filter(fn {_, mark} -> mark != "." end)
      |> Enum.map(fn {{ax, _}, _} ->
        get_next_point({ax, ry}, direction)
      end)

    next_row =
      Enum.flat_map(next_points, fn point ->
        if Enum.member?(["^", "v"], direction) do
          case Map.get(map, point) do
            "#" ->
              [{point, Map.get(map, point)}]

            "." ->
              [{point, Map.get(map, point)}]

            "]" ->
              {px, py} = point
              [{{px - 1, py}, "["}, {point, "]"}]

            "[" ->
              {px, py} = point
              [{point, "["}, {{px + 1, py}, "]"}]
          end
        else
          [{point, Map.get(map, point)}]
        end
      end)
      |> Enum.sort()
      |> Enum.uniq()

    next_acc = [next_row | acc]

    cond do
      Enum.any?(next_points, &(Map.get(map, &1) == "#")) ->
        next_acc

      Enum.all?(next_points, &(Map.get(map, &1) == ".")) ->
        next_acc

      Enum.member?(["^", "v"], direction) ->
        robot_point = {rx, if(direction == "^", do: ry - 1, else: ry + 1)}
        search_end_point(robot_point, direction, map, next_acc)

      true ->
        robot_point = hd(next_points)
        search_end_point(robot_point, direction, map, next_acc)
    end
  end

  defp get_next_point({px, py}, direction) do
    case direction do
      "^" -> {px, py - 1}
      "v" -> {px, py + 1}
      "<" -> {px - 1, py}
      ">" -> {px + 1, py}
    end
  end

  def get_map_size(map) do
    {
      map |> Enum.map(fn {{x, _}, _} -> x end) |> Enum.max() |> Kernel.+(1),
      map |> Enum.map(fn {{_, y}, _} -> y end) |> Enum.max() |> Kernel.+(1)
    }
  end

  def display_map(map, {tx, ty}) do
    0..(ty - 1)
    |> Enum.map(fn y ->
      0..(tx - 1)
      |> Enum.map(fn x ->
        Map.get(map, {x, y})
      end)
      |> Enum.join()
    end)
    |> Enum.join("\n")
  end
end

まずは初期状態を視覚化します

map_size = Robot.get_map_size(map)
map
|> Robot.display_map(map_size)
|> Kino.Text.new(terminal: true)

実行結果

##############
##......##..##
##..........##
##....[][]@.##
##....[]....##
##..........##
##############

ロボットを移動させてみましょう

{moved_map, _} =
  directions
  |> Enum.reduce({map, robot_point}, fn direction, {acc_map, acc_robot_point} ->
    {acc_map, acc_robot_point} = Robot.move({acc_map, acc_robot_point}, direction)

    IO.puts(direction)
    acc_map |> Robot.display_map(map_size) |> IO.puts()
    IO.puts("")

    {acc_map, acc_robot_point}
  end)

moved_map
|> Robot.display_map(map_size)
|> Kino.Text.new(terminal: true)

標準出力

<
##############
##......##..##
##..........##
##...[][]@..##
##....[]....##
##..........##
##############

v
##############
##......##..##
##..........##
##...[][]...##
##....[].@..##
##..........##
##############

v
##############
##......##..##
##..........##
##...[][]...##
##....[]....##
##.......@..##
##############

<
##############
##......##..##
##..........##
##...[][]...##
##....[]....##
##......@...##
##############

<
##############
##......##..##
##..........##
##...[][]...##
##....[]....##
##.....@....##
##############

^
##############
##......##..##
##...[][]...##
##....[]....##
##.....@....##
##..........##
##############

^
##############
##......##..##
##...[][]...##
##....[]....##
##.....@....##
##..........##
##############

<
##############
##......##..##
##...[][]...##
##....[]....##
##....@.....##
##..........##
##############

<
##############
##......##..##
##...[][]...##
##....[]....##
##...@......##
##..........##
##############

^
##############
##......##..##
##...[][]...##
##...@[]....##
##..........##
##..........##
##############

^
##############
##...[].##..##
##...@.[]...##
##....[]....##
##..........##
##..........##
##############

問題文と同じ移動をしています

原点により近い方は必ず "[" なので、 "[" の座標を使って計算します

get_gps_coordinate = fn map ->
  map
  |> Enum.map(fn {{x, y}, mark} ->
    case mark do
      "[" -> x + y * 100
      _ -> 0
    end
  end)
  |> Enum.sum()
end
get_gps_coordinate.(moved_map)

実行結果

618

大きい入力例でも同様に処理してみます

まずは状態を確認します

{map, directions} =
  """
  ##########
  #..O..O.O#
  #......O.#
  #.OO..O.O#
  #..O@..O.#
  #O#..O...#
  #O..O..O.#
  #.OO.O.OO#
  #....O...#
  ##########
  
  <vv>^<v^>v>^vv^v>v<>v^v<v<^vv<<<^><<><>>v<vvv<>^v^>^<<<><<v<<<v^vv^v>^
  vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<<v<^v>^<^^>>>^<v<v
  ><>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^<v>v^^<^^vv<
  <<v<^>>^^^^>>>v^<>vvv^><v<<<>^^^vv^<vvv>^>v<^^^^v<>^>vvvv><>>v^<<^^^^^
  ^><^><>>><>^^<<^^v>>><^<v>^<vv>>v>>>^v><>^v><<<<v>>v<v<v>vvv>^<><<>^><
  ^>><>^v<><^vvv<^^<><v<<<<<><^v<<<><<<^^<v<^^^><^>>^<v^><<<^>>^v<v^v<v^
  >^>>^v>vv>^<<^v<>><<><<v<<v><>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^
  <><^^>^^^<><vvvvv^v<v<<>^v<v>v<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<>
  ^^>vv<^v^v<vv>^<><v<^v>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<><<v>
  v^^>>><<^^<>>^v^<v^vv<>v^<<>^<^v^v><^<<<><<^<v><v<>vv>>v><v^<vv<>v^<<^
  """
  |> String.trim()
  |> spread_map.()
  |> parse_map.()

map_size = Robot.get_map_size(map)

map
|> Robot.display_map(map_size)
|> Kino.Text.new(terminal: true)

実行結果

####################
##....[]....[]..[]##
##............[]..##
##..[][]....[]..[]##
##....[]@.....[]..##
##[]##....[]......##
##[]....[]....[]..##
##..[][]..[]..[][]##
##........[]......##
####################

移動させてみます

robot_point = get_robot_point.(map)

{moved_map, _} =
  directions
  |> Enum.reduce({map, robot_point}, fn direction, {acc_map, acc_robot_point} ->
    Robot.move({acc_map, acc_robot_point}, direction)
  end)

moved_map
|> Robot.display_map(map_size)
|> Kino.Text.new(terminal: true)

実行結果

####################
##[].......[].[][]##
##[]...........[].##
##[]........[][][]##
##[]......[]....[]##
##..##......[]....##
##..[]............##
##..@......[].[][]##
##......[][]..[]..##
####################
get_gps_coordinate.(moved_map)

実行結果

9021

実際の入力に対して実行します

{map, directions} =
  puzzle_input
  |> spread_map.()
  |> parse_map.()

robot_point = get_robot_point.(map)
map_size = Robot.get_map_size(map)

directions
|> Enum.reduce({map, robot_point}, fn direction, {acc_map, acc_robot_point} ->
  Robot.move({acc_map, acc_robot_point}, direction)
end)
|> elem(0)
|> get_gps_coordinate.()

まとめ

問題文から ChatGPT に画像を生成してもらいました

aoc2024_day15_2.png

箱の幅が2倍になるだけで、かなり難しくなりました

5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?