Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

AoC 2023 - Day 05

2023/lang-elixir/05.livemd

AoC 2023 - Day 05

Mix.install([
  {:kino, "~> 0.11.3"}
])

Section

input_e1 =
  "day05-e1.txt"
  |> Kino.FS.file_path()
  |> File.read!()
  |> String.trim()

input =
  "day05.txt"
  |> Kino.FS.file_path()
  |> File.read!()
  |> String.trim()

[input_e1, input]
defmodule Solution do
  def part_1(input) do
    [seeds, maps] = parse(input)

    Enum.map(seeds, fn seed ->
      Enum.reduce(maps, seed, &match/2)
    end)
    |> Enum.min()
  end

  def part_2(input) do
    [seeds, steps] = parse(input)

    seeds
    |> Enum.chunk_every(2)
    |> Enum.map(fn [s1, s2] -> s1..(s1 + s2 - 1) end)
    |> steps(steps)
    |> Enum.map(fn s1.._ -> s1 end)
    |> Enum.min()
  end

  def parse(input) do
    [seeds | maps] = input |> String.split("\n\n")

    seeds =
      seeds
      |> String.split(":")
      |> List.last()
      |> String.split(" ", trim: true)
      |> Enum.map(&String.to_integer/1)

    maps =
      Enum.map(maps, fn map ->
        map
        |> String.split("\n")
        |> Enum.drop(1)
        |> Enum.map(&build_range/1)
        |> Enum.sort_by(&elem(&1, 0))
      end)

    [seeds, maps]
  end

  defp match(step, seed) do
    Enum.find_value(step, seed, fn {r1, r2, offset} ->
      if seed in r1..r2 do
        seed - offset
      end
    end)
  end

  defp steps(seed, []), do: seed

  defp steps(seed, [step | rest]) do
    seed
    |> Enum.flat_map(&step(&1, step))
    |> steps(rest)
  end

  defp step(s1..s2, []), do: [s1..s2]

  defp step(s1..s2, [{r1, r2, offset} | rest]) do
    cond do
      # within
      r1 <= s1 and s2 <= r2 ->
        [(s1 - offset)..(s2 - offset)]

      # partial @ start
      s1 < r1 and s2 in r1..r2 ->
        [
          s1..(r1 - 1),
          (r1 - offset)..(r2 - offset)
        ]

      # partial @ end?
      s1 in r1..r2 ->
        [
          (s1 - offset)..(r2 - offset)
          | step((r2 + 1)..s2, rest)
        ]

      true ->
        step(s1..s2, rest)
    end
  end

  defp build_range(string) do
    [dst, src, len] = string |> String.split(" ") |> Enum.map(&amp;String.to_integer/1)
    {src, src + len - 1, src - dst}
  end
end
Solution.parse(input)
Solution.part_1(input_e1)
Solution.part_2(input)