闘魂Elixir ── Advent of code 2023 Day 10 Part 1 を Livebook で楽しむ

<b><font color="red">$\huge{元氣ですかーーーーッ！！！}$</font></b>
<b><font color="red">$\huge{元氣があればなんでもできる！}$</font></b>

<b><font color="red">$\huge{闘魂とは己に打ち克つこと。}$</font></b>
<b><font color="red">$\huge{そして闘いを通じて己の魂を磨いていく}$</font></b>
<b><font color="red">$\huge{ことだと思います}$</font></b>

https://qiita.com/advent-calendar/2023/elixir

## はじめに

@torifukukaiou さんの ~~パク~~ リスペクト記事です

https://qiita.com/torifukukaiou/items/5361b81407b15e7c2f00

Elixir Livebook で Advent of Code 2023 の問題を解いてみます

https://adventofcode.com/2023

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

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

問題はこちら

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

## セットアップ

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

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

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

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

## 入力の取得

Day 10 の入力を取得します

![スクリーンショット 2023-12-27 13.11.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/ef3ed983-1eeb-c7ca-6bd0-f31b0af9a005.png)

## 私の答え

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

<details><summary>details</summary>

回答用のモジュールです

入力を扱いやすい形にするための `parse` と、回答を作るための `resolve` 関数を持っています

```elixir
defmodule Resolver do
  @pipe_map %{
    "-" => %{left: true, right: true},
    "|" => %{up: true, down: true},
    "F" => %{down: true, right: true},
    "7" => %{down: true, left: true},
    "L" => %{up: true, right: true},
    "J" => %{up: true, left: true},
    "S" => %{start: true, up: true, down: true, left: true, right: true}
  }

  @next_direction %{
    up: :down,
    down: :up,
    left: :right,
    right: :left,
  }

  def parse(input) do
    input
    |> String.split("\n")
    |> Enum.with_index()
    |> Enum.reduce(%{}, fn {line, row_index}, acc ->
      line_maze =
        line
        |> String.codepoints()
        |> Enum.with_index()
        |> Enum.into(%{}, fn {pipe, col_index} ->
          {
            {row_index, col_index},
            Map.get(@pipe_map, pipe, nil)
          }
        end)
        |> Map.filter(fn {_, route} -> !is_nil(route) end)

      Map.merge(acc, line_maze)
    end)
  end

  def resolve(maze) do
    {position, route} =
      maze
      |> Enum.filter(fn {_, route} ->
        Map.has_key?(route, :start)
      end)
      |> hd()

    Stream.iterate(0, & &1 + 1)
    |> Enum.reduce_while({position, route, :start}, fn index, {next_position, next_route, pre_direction} ->
      {next_position, next_route, pre_direction} =
        search_next(next_position, next_route, pre_direction, maze)
  
      IO.inspect({next_position, next_route, pre_direction})
  
      if Map.has_key?(next_route, :start) do
        {:halt, div(index + 1, 2)}
      else
        {:cont, {next_position, next_route, pre_direction}}
      end
    end)
  end

  defp search_next(position, route, pre_direction, maze) do
    [:up, :down, :left, :right]
    |> Enum.filter(& (&1 != pre_direction))
    |> Enum.map(fn direction ->
      get_next(position, route, direction, maze)
    end)
    |> Enum.filter(fn {_, next_route, _} ->
      !is_nil(next_route)
    end)
    |> hd()
  end

  defp get_next({row_index, col_index}, route, direction, maze) when is_map_key(route, direction) do
    next_position =
      case direction do
        :up -> {row_index - 1, col_index}
        :down -> {row_index + 1, col_index}
        :left -> {row_index, col_index - 1}
        :right -> {row_index, col_index + 1}
      end

    next_direction = Map.get(@next_direction, direction)

    next_route =
      maze
      |> Map.get(next_position)
      |> check_next(next_direction)

    {next_position, next_route, next_direction}
  end
  defp get_next(_, _, _, _), do: {nil, nil, nil}

  defp check_next(nil, _), do: nil
  defp check_next(next_route, next_direction) when is_map_key(next_route, next_direction) do
    next_route
  end
  defp check_next(_, _), do: nil
end
```

`parse` で各パイプを座標とルート（どの方向に行けるか）として読み取ります
スタート地点はとりあえず全方向に移動可能としておきます

入力
```
.....
.S-7.
.|.|.
.L-J.
.....
```

パース結果
```elixir
%{
  {1, 1} => %{start: true, up: true, down: true, left: true, right: true},
  {1, 2} => %{left: true, right: true},
  {1, 3} => %{down: true, left: true},
  {2, 1} => %{up: true, down: true},
  {2, 3} => %{up: true, down: true},
  {3, 1} => %{up: true, right: true},
  {3, 2} => %{left: true, right: true},
  {3, 3} => %{up: true, left: true}
}
```

`resolve` では、スタート地点から開始して、次に進めるパイプを探索していきます

スタート地点に到達したらループの完成です

行き止まりがなく、パイプが必ずループしている前提なので、スタート地点から最も遠い地点の距離 = ループの全長 / 2 となります

</details>

## まとめ

今回もかなり愚直に解きました

前のトランプと同様、迷路というテーマが面白かったです

実装としては関数の `when` で `is_map_key` を使えたのが良かったです

Part 2 はこちら

https://qiita.com/RyoWakabayashi/items/2c968b68ebc31cc56100