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

Day 5: If You Give A Seed A Fertilizer

2023/day-05.livemd

Day 5: If You Give A Seed A Fertilizer

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

Day 5

sample_input = Kino.Input.textarea("Paste Sample Input")
real_input = Kino.Input.textarea("Paste Real Input")
defmodule SeedRules.PartOne do
  def parse(input) do
    input
    |> Kino.Input.read()
    |> String.split("\n\n", trim: true)
    |> Enum.reduce([], &process_segment/2)
    |> Enum.min()
  end

  defp process_segment("seeds: " <> rest, []) do
    rest |> String.split(" ") |> Enum.map(&amp;String.to_integer/1)
  end

  defp process_segment(rules_raw, previous_values) do
    rules = parse_rules(rules_raw)

    Enum.map(previous_values, fn value ->
      rule = Enum.find(rules, &amp; &amp;1.condition.(value))
      rule.apply.(value)
    end)
  end

  defp parse_rules(rules_raw) do
    [_label | [rules]] = String.split(rules_raw, ":", trim: true)

    rules
    |> String.split("\n", trim: true)
    |> Enum.map(fn line ->
      [dest_start, src_start, len] = line |> String.split(" ") |> Enum.map(&amp;String.to_integer/1)

      %{
        condition: fn no -> src_start <= no &amp;&amp; no < src_start + len end,
        apply: fn no -> no + (dest_start - src_start) end
      }
    end)
    |> Enum.concat([identity()])
  end

  defp identity, do: %{condition: fn _ -> true end, apply: &amp; &amp;1}
end
SeedRules.PartOne.parse(sample_input)
SeedRules.PartOne.parse(real_input)
defmodule SeedRules.PartTwo do
  def parse(input) do
    [seeds | rules] =
      input
      |> Kino.Input.read()
      |> String.split("\n\n", trim: true)
      |> Enum.map(&amp;process_segment/1)

    rules
    |> Enum.reduce(seeds, fn ruleset, seed_values ->
      Enum.flat_map(seed_values, &amp;apply_rules(&amp;1, ruleset))
    end)
    |> Enum.map(&amp;elem(&amp;1, 0))
    |> Enum.min()
  end

  defp apply_rules(seeds, []), do: [seeds]

  defp apply_rules({seed_start, seed_length} = seeds, [rule | rest_rules] = rules) do
    cond do
      seed_start + seed_length < rule.start ->
        apply_rules(seeds, rest_rules)

      seed_start > rule.start + rule.length ->
        apply_rules(seeds, rest_rules)

      seed_start >= rule.start and
          seed_start + seed_length <= rule.start + rule.length ->
        [{rule.apply.(seed_start), seed_length}]

      seed_start < rule.start ->
        [
          {seed_start, rule.start - seed_start}
          | apply_rules({rule.start, seed_length - (rule.start - seed_start)}, rules)
        ]

      true ->
        [
          {rule.apply.(seed_start), rule.length - (seed_start - rule.start)}
          | apply_rules(
              {rule.start + rule.length, seed_start + seed_length - (rule.start + rule.length)},
              rest_rules
            )
        ]
    end
  end

  defp process_segment("seeds: " <> rest) do
    rest
    |> String.split(" ")
    |> Enum.chunk_every(2)
    |> Enum.map(fn [start, len] ->
      {String.to_integer(start), String.to_integer(len)}
    end)
  end

  defp process_segment(rules_raw) do
    [_label | [rules]] = String.split(rules_raw, ":", trim: true)

    rules
    |> String.split("\n", trim: true)
    |> Enum.map(fn line ->
      [dest_start, src_start, len] = line |> String.split(" ") |> Enum.map(&amp;String.to_integer/1)

      %{
        start: src_start,
        length: len,
        apply: fn no -> no + (dest_start - src_start) end
      }
    end)
    |> Enum.sort_by(&amp; &amp;1.start)
  end
end
SeedRules.PartTwo.parse(sample_input)
SeedRules.PartTwo.parse(real_input)