Advent of code 2023 Day 14 Part 1 を Livebook で楽しむ

## はじめに

Advent of code 2023 Day 14 の Part 1 を解きます

問題文はこちら

https://adventofcode.com/2023/day/14

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

https://github.com/RyoWakabayashi/elixir-learning/blob/main/livebooks/aoc/2023/2023_day14_part1.livemd

## セットアップ

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

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

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

https://qiita.com/RyoWakabayashi/items/3d3abcf9134cee4dff05

## 入力の取得

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

![スクリーンショット 2024-12-04 9.01.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/3f5f78f5-7b1a-ac12-4f32-b37ecb1df815.png)

## 私の答え

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

<details><summary>回答</summary>

まずは入力例の小さいマップで解いてみましょう

入力を文字毎の配列に変換します

```elixir
map =
  """
  O....#....
  O.OO#....#
  .....##...
  OO.#O....O
  .O.....O#.
  O.#..O.#.#
  ..O..#O..O
  .......O..
  #....###..
  #OO..#....
  """
  |> String.trim()
  |> String.split("\n")
  |> Enum.map(fn row ->
    String.codepoints(row)
  end)
```

実行結果

```elixir
[
  ["O", ".", ".", ".", ".", "#", ".", ".", ".", "."],
  ["O", ".", "O", "O", "#", ".", ".", ".", ".", "#"],
  [".", ".", ".", ".", ".", "#", "#", ".", ".", "."],
  ["O", "O", ".", "#", "O", ".", ".", ".", ".", "O"],
  [".", "O", ".", ".", ".", ".", ".", "O", "#", "."],
  ["O", ".", "#", ".", ".", "O", ".", "#", ".", "#"],
  [".", ".", "O", ".", ".", "#", "O", ".", ".", "O"],
  [".", ".", ".", ".", ".", ".", ".", "O", ".", "."],
  ["#", ".", ".", ".", ".", "#", "#", "#", ".", "."],
  ["#", "O", "O", ".", ".", "#", ".", ".", ".", "."]
]
```

北にスライドさせる = 上方向にずらす、ということですが、列方向の操作はやりづらいので、縦横を入れ替えるために 90 度回転します

```elixir
turned_map =
  map
  |> Enum.zip()
  |> Enum.map(fn col ->
    Tuple.to_list(col)
  end)
  |> Enum.reverse()
```

実行結果

```elixir
[
  [".", "#", ".", "O", ".", "#", "O", ".", ".", "."],
  [".", ".", ".", ".", "#", ".", ".", ".", ".", "."],
  [".", ".", ".", ".", "O", "#", ".", "O", "#", "."],
  [".", ".", "#", ".", ".", ".", "O", ".", "#", "."],
  ["#", ".", "#", ".", ".", "O", "#", ".", "#", "#"],
  [".", "#", ".", "O", ".", ".", ".", ".", ".", "."],
  [".", "O", ".", "#", ".", ".", ".", ".", ".", "."],
  [".", "O", ".", ".", ".", "#", "O", ".", ".", "O"],
  [".", ".", ".", "O", "O", ".", ".", ".", ".", "O"],
  ["O", "O", ".", "O", ".", "O", ".", ".", "#", "#"]
]
```

この状態で左方向にずらせば北にずらしたのと同じことになります

岩をスライドさせるためのモジュールを定義します

```elixir
defmodule Rock do
  def turn(map) do
    map
    |> Enum.zip()
    |> Enum.map(fn col ->
      Tuple.to_list(col)
    end)
    |> Enum.reverse()
  end

  def slide(map) do
    Enum.map(map, fn row ->
      slide(row, length(row) - 1)
    end)
  end
  
  def slide(row, 0), do: row
  def slide(row, current_index) do
    current_block = Enum.at(row, current_index)
    next_block = Enum.at(row, current_index - 1)

    case {current_block, next_block} do
      {"O", "."} ->
        row
        |> List.update_at(current_index, fn _ -> "." end)
        |> List.update_at(current_index - 1, fn _ -> "O" end)
        |> slide(length(row) - 1)
      _ ->
        slide(row, current_index - 1)
    end
  end

  def sum_load(map) do
    map
    |> Enum.map(fn row ->
      row
      |> Enum.reverse()
      |> Enum.with_index(1)
      |> Enum.map(fn {mark, index} ->
        case mark do
          "O" -> index
          _ -> 0
        end
      end)
      |> Enum.sum()
    end)
    |> Enum.sum()
  end
end
```

ある位置の記号と右隣の記号が `.O` であればスライドできるため、次の状態は `O.` になります

それ以外の `..`, `O.`, `OO`, `##`, `#O`, `O#`, `#.`, `.#` では、どのパターンもスライドできないので変化しません

順次岩をスライドさせていくため、このスライドを右側から順に行い、スライドできたら（状態が変わったら）右端から再度スライドしていくことで、全ての岩をスライドさせることができます

実行例

- `.O#.OO.` 初期状態
- `.O#O.O.` 右端から順にチェックしていくと、2番目の岩を動かすことができた
- `.O#OO..` 改めて右端から順にチェックしていくと、3番目の岩を動かすことができた
- `O.#OO..` 改めて右端から順にチェックしていくと、1番目の岩を動かすことができた
- `O.#OO..` 最後に左端までチェックして何も動かすことができなかったのでスライド終了

これを全ての行に対して実行すれば良いことになります

実際に入力例でスライドさせてみましょう

```elixir
map
|> Rock.turn()
|> Rock.slide()
```

実行結果

```elixir
[
  [".", "#", "O", ".", ".", "#", "O", ".", ".", "."],
  [".", ".", ".", ".", "#", ".", ".", ".", ".", "."],
  ["O", ".", ".", ".", ".", "#", "O", ".", "#", "."],
  [".", ".", "#", "O", ".", ".", ".", ".", "#", "."],
  ["#", ".", "#", "O", ".", ".", "#", ".", "#", "#"],
  [".", "#", "O", ".", ".", ".", ".", ".", ".", "."],
  ["O", ".", ".", "#", ".", ".", ".", ".", ".", "."],
  ["O", ".", ".", ".", ".", "#", "O", "O", ".", "."],
  ["O", "O", "O", ".", ".", ".", ".", ".", ".", "."],
  ["O", "O", "O", "O", ".", ".", ".", ".", "#", "#"]
]
```

全ての丸い岩が可能な分だけ左に移動しました

左から順に重量の影響は 10, 9, 8, ..., 3, 2, 1 となっているため、左右反転させて岩の `列番号 + 1` を合計します

```elixir
map
|> Rock.turn()
|> Rock.slide()
|> Rock.sum_load()
```

実行結果は `136` となり、問題文の結果と一致しました

同様の操作を実際の入力に対して実行しましょう

```elixir
map =
  puzzle_input
  |> String.split("\n")
  |> Enum.map(fn row ->
    String.codepoints(row)
  end)
```

```elixir
map
|> Rock.turn()
|> Rock.slide()
|> Rock.sum_load()
```

</details>

## まとめ

ややこしくはありますが、ここまでは考えやすかったです

Part 2 がもっとややこしくて考えないといけないことが多く、記事を分けました

https://qiita.com/RyoWakabayashi/private/8deddf2172011b744c5e