闘魂Elixir ── Advent of code 2023 Day 5 Part 2 を 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/7dfd1439b1d6d82d5586

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

https://adventofcode.com/2023

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

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

問題はこちら

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

Part 1 はこちら

https://qiita.com/RyoWakabayashi/items/b59f5c3ca40dc1f20cf3

## セットアップ

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

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

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

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

## 入力の取得

Day 5 の入力を取得します

![スクリーンショット 2023-12-26 15.45.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/445befcf-8b89-bccb-be16-889b62c3f62d.png)

## 私の答え

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

<details><summary>details</summary>

回答用のモジュールです

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

```elixir
defmodule Resolver do
  def parse(input) do
    input
    |> String.split("\n\n")
    |> Enum.into(%{}, fn group ->
      group
      |> String.split("\n")
      |> parse_group()
    end)
  end

  defp parse_group(group) when length(group) == 1 do
    seeds =
      group
      |> hd()
      |> String.split(" ")
      |> tl()
      |> Enum.map(&String.to_integer(&1))
      |> Enum.chunk_every(2)
      |> Enum.map(fn [start, len] ->
        %{
          start_index: start,
          end_index: start + len - 1
        }
      end)

    {:seeds, seeds}
  end

  defp parse_group(group) do
    group_name =
      group
      |> hd()
      |> String.slice(0..-6)
      |> String.replace("-", "_")
      |> String.to_atom()

    mappings =
      group
      |> tl()
      |> Enum.map(fn line ->
        [dst, src, len] =
          line
          |> String.split(" ")
          |> Enum.map(&String.to_integer(&1))

        %{
          src: src,
          dst: dst,
          len: len
        }
      end)

    {group_name, mappings}
  end

  def resolve(maps) do
    0
    |> Stream.iterate(& &1 + 1)
    |> Enum.reduce_while(nil, fn location, _acc ->
      seed =
        location
        |> reverse_search(maps.humidity_to_location)
        |> reverse_search(maps.temperature_to_humidity)
        |> reverse_search(maps.light_to_temperature)
        |> reverse_search(maps.water_to_light)
        |> reverse_search(maps.fertilizer_to_water)
        |> reverse_search(maps.soil_to_fertilizer)
        |> reverse_search(maps.seed_to_soil)

      maps.seeds
      |> Enum.any?(fn %{start_index: start_index, end_index: end_index} ->
        seed >= start_index and seed <= end_index
      end)
      |> if do
        {:halt, location}
      else
        {:cont, nil}
      end
    end)
  end

  defp reverse_search(key, mappings) do
    target_mapping =
      Enum.find(mappings, fn mapping ->
        key >= mapping.dst and key <= (mapping.dst + mapping.len - 1)
      end)

    if is_nil(target_mapping) do
      key
    else
      target_mapping.src + key - target_mapping.dst
    end
  end
end
```

`parse` は Part 1 では無駄に難しく考えていました

`\n\n` で先に区切れば単純に空白行でグループ化できますね

`resolve` ではついに計算量を考慮しなければならなくなりました

`seeds` が範囲指定になったため、 Part 1 と同じロジックでは、いつまで経っても計算が終わりません

最小値を求める問題なので、 location の方から逆引きするようにして、 0 から順に種　ID が seeds の範囲内に入っているかチェックしていきます

</details>

## まとめ

いよいよ計算量を考えないといけなくなリました

ここからが本番、という感じですね