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

2023 day 5

2023/elixir/day-5.livemd

2023 day 5

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

Section

input = Kino.Input.textarea("input")
input = Kino.Input.read(input)

sample_input = """
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
"""

Part 1 & Part 2

defmodule Day5.Naive do
  defmodule Part1 do
    def solve(input_str) do
      {seeds, mapper} =
        input_str
        |> Day5.Naive.parse(:normal)

      seeds
      |> Enum.map(mapper)
      |> Enum.min()
    end
  end

  defmodule Part2 do
    def solve(input_str) do
      {seeds, mapper} =
        input_str
        |> Day5.Naive.parse(:reverse)

      included_in_seeds? = build_seeds_container(seeds)

      1
      |> Stream.iterate(&(&1 + 1))
      |> Enum.find(fn i ->
        source = mapper.(i)
        included_in_seeds?.(source)
      end)
    end

    defp build_seeds_container(seeds) do
      seeds
      |> Enum.chunk_every(2)
      |> Enum.reduce(fn _ -> false end, fn [start, range], acc_fn ->
        fn
          target ->
            if target in start..(start + range - 1) do
              true
            else
              acc_fn.(target)
            end
        end
      end)
    end
  end

  def parse(input_str, :normal = mode) do
    [seeds | mappers] =
      input_str
      |> String.split("\n\n", trim: true)
      |> Enum.map(&parse_paragraph(&1, mode))

    mapper =
      mappers
      |> Enum.reverse()
      |> Enum.reduce(&Function.identity/1, fn {_, mapper}, acc_fn ->
        &acc_fn.(mapper.(&1))
      end)

    {seeds, mapper}
  end

  def parse(input_str, :reverse = mode) do
    [seeds | mappers] =
      input_str
      |> String.split("\n\n", trim: true)
      |> Enum.map(&parse_paragraph(&1, mode))

    mapper =
      mappers
      |> Enum.reduce(&Function.identity/1, fn {_, mapper}, acc_fn ->
        &acc_fn.(mapper.(&1))
      end)

    {seeds, mapper}
  end

  defp parse_paragraph("seeds: " <> seeds, _mode) do
    seeds
    |> String.split(" ", trim: true)
    |> Enum.map(&amp;String.to_integer/1)
  end

  defp parse_paragraph("seed-to-soil map:\n" <> lines, mode) do
    {{:seed, :soil}, parse_mapper(lines, mode)}
  end

  defp parse_paragraph("soil-to-fertilizer map:\n" <> lines, mode) do
    {{:soil, :fertilizer}, parse_mapper(lines, mode)}
  end

  defp parse_paragraph("fertilizer-to-water map:\n" <> lines, mode) do
    {{:fertilizer, :water}, parse_mapper(lines, mode)}
  end

  defp parse_paragraph("water-to-light map:\n" <> lines, mode) do
    {{:water, :light}, parse_mapper(lines, mode)}
  end

  defp parse_paragraph("light-to-temperature map:\n" <> lines, mode) do
    {{:light, :temperature}, parse_mapper(lines, mode)}
  end

  defp parse_paragraph("temperature-to-humidity map:\n" <> lines, mode) do
    {{:temperature, :humidity}, parse_mapper(lines, mode)}
  end

  defp parse_paragraph("humidity-to-location map:\n" <> lines, mode) do
    {{:humidity, :location}, parse_mapper(lines, mode)}
  end

  defp parse_mapper(lines, mode) do
    lines
    |> String.split(["\n", " "], trim: true)
    |> Enum.chunk_every(3)
    |> Enum.map(fn [dest, source, range] ->
      dest = String.to_integer(dest)
      source = String.to_integer(source)
      range = String.to_integer(range)

      if mode == :reverse do
        {dest, source, range}
      else
        {source, dest, range}
      end
    end)
    |> build_mapper()
  end

  defp build_mapper(maps) do
    maps
    |> Enum.reduce(&amp;Function.identity/1, fn {source, dest, range}, acc_fun ->
      fn
        target when target in source..(source + range - 1) ->
          target + (dest - source)

        out_of_range ->
          acc_fun.(out_of_range)
      end
    end)
  end
end
Day5.Naive.Part1.solve(input)
Day5.Naive.Part2.solve(input)