LoginSignup
9
1

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

Last updated at Posted at 2023-12-26

$\huge{元氣ですかーーーーッ!!!}$
$\huge{元氣があればなんでもできる!}$

$\huge{闘魂とは己に打ち克つこと。}$
$\huge{そして闘いを通じて己の魂を磨いていく}$
$\huge{ことだと思います}$

はじめに

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

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

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

問題はこちら

セットアップ

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

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

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

入力の取得

Day 5 の入力を取得します

スクリーンショット 2023-12-26 15.45.30.png

私の答え

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

details

回答用のモジュールです

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

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

  defp split_by_blank_line(lines, acc) do
    {head, tail} =
      Enum.split_while(lines, fn line -> line != "" end)

    if Enum.count(tail) == 0 do
      acc ++ [head]
    else
      tail
      |> tl()
      |> split_by_blank_line(acc ++ [head])
    end
  end

  defp parse_group(group) when length(group) == 1 do
    seeds =
      group
      |> hd()
      |> String.split(" ")
      |> tl()
      |> Enum.map(&String.to_integer(&1))

    {: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
    maps.seeds
    |> Enum.map(fn seed ->
      seed
      |> search(maps.seed_to_soil)
      |> search(maps.soil_to_fertilizer)
      |> search(maps.fertilizer_to_water)
      |> search(maps.water_to_light)
      |> search(maps.light_to_temperature)
      |> search(maps.temperature_to_humidity)
      |> search(maps.humidity_to_location)
    end)
    |> Enum.min()
  end

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

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

parse の時点でなかなか複雑です

入力の各行を空白行区切りでグループ化しています

Enum.split_while で空白行前後に分けるのを再帰する形にしましたが、余計に複雑になった気がします

seeds は種 ID の配列、それ以外は %{src: <変換元開始インデックス>, dst: <変換先開始インデックス>, len: <変換範囲の長さ>} の形式のマップの配列にしています

パースの例は以下のようになります

入力

seeds: 79 14 55 13

seed-to-soil map:
50 98 2
52 50 48

soil-to-fertilizer map:
0 15 37
37 52 2
39 0 15

fertilizer-to-water map:
49 53 8
0 11 42
42 0 7
57 7 4

water-to-light map:
88 18 7
18 25 70

light-to-temperature map:
45 77 23
81 45 19
68 64 13

temperature-to-humidity map:
0 69 1
1 0 69

humidity-to-location map:
60 56 37
56 93 4

パース結果

%{
  seeds: [79, 14, 55, 13],
  seed_to_soil: [%{len: 2, dst: 50, src: 98}, %{len: 48, dst: 52, src: 50}],
  soil_to_fertilizer: [
    %{len: 37, dst: 0, src: 15},
    %{len: 2, dst: 37, src: 52},
    %{len: 15, dst: 39, src: 0}
  ],
  fertilizer_to_water: [
    %{len: 8, dst: 49, src: 53},
    %{len: 42, dst: 0, src: 11},
    %{len: 7, dst: 42, src: 0},
    %{len: 4, dst: 57, src: 7}
  ],
  water_to_light: [%{len: 7, dst: 88, src: 18}, %{len: 70, dst: 18, src: 25}],
  light_to_temperature: [
    %{len: 23, dst: 45, src: 77},
    %{len: 19, dst: 81, src: 45},
    %{len: 13, dst: 68, src: 64}
  ],
  temperature_to_humidity: [%{len: 1, dst: 0, src: 69}, %{len: 69, dst: 1, src: 0}],
  humidity_to_location: [%{len: 37, dst: 60, src: 56}, %{len: 4, dst: 56, src: 93}]
}

resolve では用意した変換マップで各 ID を変換していきます

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

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

変換元インデックスが変換範囲内にあれば変換し、なければそのままの値を返します

ここまで用意した後、一気にパイプを繋げられるのが気持ちいいです

      seed
      |> search(maps.seed_to_soil)
      |> search(maps.soil_to_fertilizer)
      |> search(maps.fertilizer_to_water)
      |> search(maps.water_to_light)
      |> search(maps.light_to_temperature)
      |> search(maps.temperature_to_humidity)
      |> search(maps.humidity_to_location)

まとめ

問題を解く以上に入力をパースするのが大変でした

Part 2 はこちら

9
1
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
9
1