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

Day 2 - Advent of Code 2024

lib/advent_of_code/2024/day-02.livemd

Day 2 - Advent of Code 2024

Mix.install([:kino, :benchee])

Links

Prompt

— Day 2: Red-Nosed Reports —

Fortunately, the first location The Historians want to search isn’t a long walk from the Chief Historian’s office.

While the Red-Nosed Reindeer nuclear fusion/fission plant appears to contain no sign of the Chief Historian, the engineers there run up to you as soon as they see you. Apparently, they still talk about the time Rudolph was saved through molecular synthesis from a single electron.

They’re quick to add that - since you’re already here - they’d really appreciate your help analyzing some unusual data from the Red-Nosed reactor. You turn to check if The Historians are waiting for you, but they seem to have already divided into groups that are currently searching every corner of the facility. You offer to help with the unusual data.

The unusual data (your puzzle input) consists of many reports, one report per line. Each report is a list of numbers called levels that are separated by spaces. For example:

7 6 4 2 1
1 2 7 8 9
9 7 6 2 1
1 3 2 4 5
8 6 4 4 1
1 3 6 7 9

This example data contains six reports each containing five levels.

The engineers are trying to figure out which reports are safe. The Red-Nosed reactor safety systems can only tolerate levels that are either gradually increasing or gradually decreasing. So, a report only counts as safe if both of the following are true:

  • The levels are either all increasing or all decreasing.

  • Any two adjacent levels differ by at least one and at most three. In the example above, the reports can be found safe or unsafe by checking those rules:

  • 7 6 4 2 1: Safe because the levels are all decreasing by 1 or 2.

  • 1 2 7 8 9: Unsafe because 2 7 is an increase of 5.

  • 9 7 6 2 1: Unsafe because 6 2 is a decrease of 4.

  • 1 3 2 4 5: Unsafe because 1 3 is increasing but 3 2 is decreasing.

  • 8 6 4 4 1: Unsafe because 4 4 is neither an increase or a decrease.

  • 1 3 6 7 9: Safe because the levels are all increasing by 1, 2, or 3. So, in this example, 2 reports are safe.

Analyze the unusual data from the engineers. How many reports are safe?

To begin, get your puzzle input.

— Part Two —

The engineers are surprised by the low number of safe reports until they realize they forgot to tell you about the Problem Dampener.

The Problem Dampener is a reactor-mounted module that lets the reactor safety systems tolerate a single bad level in what would otherwise be a safe report. It’s like the bad level never happened!

Now, the same rules apply as before, except if removing a single level from an unsafe report would make it safe, the report instead counts as safe.

More of the above example’s reports are now safe:

  • 7 6 4 2 1: Safe without removing any level.
  • 1 2 7 8 9: Unsafe regardless of which level is removed.
  • 9 7 6 2 1: Unsafe regardless of which level is removed.
  • 1 3 2 4 5: Safe by removing the second level, 3.
  • 8 6 4 4 1: Safe by removing the third level, 4.
  • 1 3 6 7 9: Safe without removing any level. Thanks to the Problem Dampener, 4 reports are actually safe!

Update your analysis by handling situations where the Problem Dampener can remove a single level from unsafe reports. How many reports are now safe?

Although it hasn’t changed, you can still get your puzzle input.

Input

input = Kino.Input.textarea("Please paste your input file:")
input = input |> Kino.Input.read()
# input = AdventOfCode.Input.get!(1, 2024)
"90 91 93 96 93\n3 5 7 10 11 11\n35 37 39 42 46\n67 70 72 74 79\n9 12 13 16 15 16 19\n48 51 52 55 58 61 58 57\n3 4 7 9 8 9 9\n22 25 28 30 28 32\n38 41 44 45 42 49\n54 57 59 59 61\n54 55 55 56 58 55\n66 68 70 71 72 72 75 75\n32 35 35 38 42\n22 24 26 28 28 31 34 41\n55 57 59 63 64\n18 21 25 26 28 27\n39 40 41 43 47 49 50 50\n10 11 14 17 20 22 26 30\n35 36 39 40 44 45 52\n57 59 66 67 70\n15 17 19 22 29 26\n66 69 72 75 78 83 83\n73 76 78 83 86 90\n23 26 28 35 36 41\n83 81 82 85 88 90 92 93\n4 2 3 4 1\n8 7 8 9 11 13 13\n71 70 71 73 76 80\n22 20 21 22 25 31\n80 78 81 83 82 84 87 88\n14 13 16 19 18 19 20 18\n69 68 65 67 67\n57 55 56 55 59\n5 4 7 9 11 13 12 18\n91 88 88 90 92 93\n47 45 48 48 46\n58 56 59 60 63 63 63\n39 37 40 43 43 45 49\n34 33 36 36 43\n13 11 13 17 20 23 25\n11 8 12 14 12\n2 1 5 7 7\n8 7 10 14 18\n38 36 38 40 41 45 48 53\n68 67 72 75 77 79\n25 24 26 29 30 37 39 37\n31 29 31 37 37\n64 61 62 69 72 76\n5 2 5 11 14 15 22\n55 55 58 60 61 64 65\n59 59 61 63 66 65\n2 2 5 7 9 12 12\n29 29 31 34 35 38 39 43\n42 42 44 46 49 51 54 61\n89 89 90 93 96 94 96 98\n41 41 39 40 41 43 45 42\n36 36 38 35 37 37\n1 1 4 2 6\n36 36 39 41 40 42 47\n29 29 29 32 35 36\n24 24 26 29 31 31 29\n58 58 58 60 60\n56 56 56 58 62\n73 73 76 76 79 86\n49 49 52 56 59\n11 11 12 13 17 18 15\n63 63 67 70 70\n82 82 86 89 90 91 94 98\n38 38 40 42 45 49 54\n16 16 21 22 23\n45 45 51 54 51\n6 6 7 10 13 14 20 20\n22 22 23 29 30 32 33 37\n7 7 13 14 21\n81 85 87 89 90 92 95 97\n75 79 80 81 79\n56 60 61 63 65 65\n67 71 74 75 79\n4 8 11 13 15 18 23\n83 87 85 87 90 93 95\n90 94 95 93 94 93\n87 91 92 93 96 94 94\n34 38 39 36 38 39 43\n4 8 9 7 10 17\n23 27 28 30 33 33 34 36\n11 15 15 17 16\n38 42 44 47 48 49 49 49\n43 47 49 49 52 55 59\n79 83 85 85 92\n16 20 24 26 29 30\n77 81 83 87 90 92 89\n33 37 39 42 44 48 51 51\n71 75 79 81 85\n69 73 76 77 81 88\n33 37 42 44 47 50 53\n13 17 20 23 28 26\n51 55 56 59 60 62 67 67\n35 39 41 43 49 52 56\n60 64 70 71 76\n25 32 34 37 38 41\n1 6 8 10 12 9\n20 27 29 30 30\n52 59 60 62 63 64 65 69\n71 78 80 81 84 85 90\n64 71 73 71 72 73 76\n67 72 75 76 74 76 77 76\n38 43 40 41 41\n2 8 11 9 13\n72 79 80 79 80 86\n21 26 26 29 31\n72 78 79 79 81 83 81\n76 81 84 84 85 85\n42 47 50 53 53 54 58\n14 21 23 25 26 29 29 35\n83 88 90 93 97 98\n17 22 26 29 30 31 28\n46 52 54 58 58\n22 28 32 33 37\n65 71 73 77 82\n64 71 73 75 78 81 88 89\n42 47 50 55 56 54\n69 74 77 79 85 85\n54 59 62 68 72\n40 45 46 51 53 55 61\n55 53 50 47 44 43 40 42\n94 91 90 89 86 85 85\n52 50 47 46 44 40\n52 49 47 45 43 38\n88 87 88 85 84 82 81 78\n95 93 96 93 94\n84 83 84 83 83\n70 67 65 64 61 64 60\n98 97 98 97 95 94 92 86\n29 27 24 24 22\n34 31 30 30 28 29\n83 81 78 78 77 75 75\n60 59 59 58 57 55 52 48\n97 96 96 95 93 88\n50 49 47 43 42 41 39\n45 43 39 38 37 40\n91 89 85 83 83\n20 18 14 11 7\n82 81 79 76 75 71 66\n45 43 42 40 33 30 28 25\n92 91 85 84 85\n30 29 24 21 18 15 14 14\n31 28 27 26 21 18 14\n38 35 34 33 31 28 22 17\n46 47 45 43 41\n89 90 88 86 84 83 84\n68 71 69 68 66 66\n71 73 72 69 65\n44 46 43 41 38 32\n8 11 9 12 11 10\n49 51 49 48 50 52\n75 76 79 76 76\n67 70 71 70 66\n51 53 54 53 50 48 47 42\n67 69 66 65 65 62\n57 59 59 58 55 53 54\n66 67 66 66 63 63\n49 50 49 46 46 43 40 36\n50 52 52 50 49 47 40\n22 23 20 16 13 11 10 8\n29 31 30 26 23 26\n46 47 43 42 42\n65 68 67 64 63 59 55\n68 69 67 64 60 58 53\n87 90 87 82 80 79 78 77\n43 44 43 41 34 37\n13 14 13 11 10 4 4\n77 78 76 74 69 67 63\n69 71 69 63 61 54\n32 32 29 26 25 22 20 19\n99 99 98 97 96 98\n91 91 89 88 86 86\n26 26 23 21 17\n45 45 43 42 40 37 36 30\n41 41 39 42 39 37\n7 7 6 8 7 4 2 4\n30 30 33 32 31 29 27 27\n58 58 55 53 50 53 49\n31 31 28 31 25\n35 35 35 32 30\n46 46 46 43 44\n38 38 38 37 37\n77 77 74 71 71 69 65\n70 70 68 65 65 62 60 54\n60 60 58 54 51 48 45 43\n32 32 28 27 26 23 21 24\n43 43 41 40 36 36\n72 72 70 66 62\n25 25 23 19 13\n68 68 67 65 62 61 56 54\n74 74 71 69 67 60 59 61\n40 40 37 32 32\n73 73 66 64 61 57\n47 47 41 40 35\n84 80 77 75 73 70 68\n93 89 88 86 85 82 80 81\n35 31 30 28 27 26 25 25\n28 24 22 21 18 17 13\n34 30 28 27 25 22 20 15\n17 13 10 7 10 7\n95 91 94 92 90 92\n20 16 13 12 13 11 9 9\n58 54 53 52 54 52 48\n86 82 83 82 77\n28 24 21 21 20\n97 93 93 91 92\n28 24 23 20 18 18 18\n38 34 32 32 30 28 27 23\n53 49 47 46 45 44 44 39\n69 65 62 61 57 56 53\n48 44 42 40 39 35 38\n73 69 67 63 63\n59 55 5" <> ...

Solution

defmodule Day02 do
  defdelegate parse(input), to: __MODULE__.Input

  def part1(input) do
    input
    |> parse()
    |> Enum.count(&amp;safe?/1)
  end

  def part2(input) do
    input
    |> parse()
    |> Enum.count(fn line ->
      0..(length(line) - 1)
      |> Enum.any?(fn index ->
        line |> List.delete_at(index) |> safe?()
      end)
    end)
  end

  defp safe?(line, dir \\ nil)
  defp safe?([_a], _), do: true
  defp safe?([a | [b | _]], _) when a == b, do: false
  defp safe?([a | [b | _]] = line, nil) when b > a, do: safe?(line, :asc)
  defp safe?([a | [b | _]] = line, nil) when b < a, do: safe?(line, :desc)
  defp safe?([a | [b | _]], :asc) when b < a, do: false
  defp safe?([a | [b | _]], :desc) when b > a, do: false
  defp safe?([a | [b | _]], _) when abs(a - b) < 1, do: false
  defp safe?([a | [b | _]], _) when abs(a - b) > 3, do: false
  defp safe?([_ | tail], dir), do: safe?(tail, dir)

  defmodule Input do
    def parse(input) when is_binary(input) do
      input
      |> String.splitter("\n", trim: true)
      |> parse()
    end

    def parse(input) do
      Stream.map(input, &amp;parse_line/1)
    end

    def parse_line(line) do
      line
      |> String.split(" ")
      |> Enum.map(&amp;String.to_integer/1)
    end
  end
end
{:module, Day02, <<70, 79, 82, 49, 0, 0, 13, ...>>,
 {:module, Day02.Input, <<70, 79, 82, ...>>, {:parse_line, 1}}}

Analyze the unusual data from the engineers. How many reports are safe?

Your puzzle answer was 421.

Day02.part1(input)
421

Update your analysis by handling situations where the Problem Dampener can remove a single level from unsafe reports. How many reports are now safe?

Your puzzle answer was 476.

Day02.part2(input)
476

Both parts of this puzzle are complete! They provide two gold stars: **

At this point, you should return to your Advent calendar and try another puzzle.

If you still want to see it, you can get your puzzle input.

Tests

ExUnit.start(auto_run: false)

defmodule Day02Test do
  use ExUnit.Case, async: false

  setup_all do
    [
      input: "7 6 4 2 1\n1 2 7 8 9\n9 7 6 2 1\n1 3 2 4 5\n8 6 4 4 1\n1 3 6 7 9"
    ]
  end

  describe "part1/1" do
    test "returns expected value", %{input: input} do
      assert Day02.part1(input) == 2
    end
  end

  describe "part2/1" do
    test "returns expected value", %{input: input} do
      assert Day02.part2(input) == 4
    end
  end
end

ExUnit.run()
..
Finished in 0.00 seconds (0.00s async, 0.00s sync)
2 tests, 0 failures

Randomized with seed 954854
%{total: 2, failures: 0, excluded: 0, skipped: 0}

Benchmarking

defmodule Benchmarking do
  # https://github.com/bencheeorg/benchee
  def run(input) do
    Benchee.run(
      %{
        "Part 1" => fn -> Day02.part1(input) end,
        "Part 2" => fn -> Day02.part2(input) end
      },
      memory_time: 2,
      reduction_time: 2
    )

    nil
  end
end
{:module, Benchmarking, <<70, 79, 82, 49, 0, 0, 7, ...>>, {:run, 1}}
Benchmarking.run(input)
Operating System: macOS
CPU Information: Apple M1
Number of Available Cores: 8
Available memory: 8 GB
Elixir 1.15.7
Erlang 26.1.1

Benchmark suite executing with the following configuration:
warmup: 2 s
time: 5 s
memory time: 2 s
reduction time: 2 s
parallel: 1
inputs: none specified
Estimated total run time: 22 s

Benchmarking Part 1 ...
Benchmarking Part 2 ...

Name             ips        average  deviation         median         99th %
Part 1        2.09 K      477.73 μs     ±3.69%      475.11 μs      524.52 μs
Part 2        1.50 K      668.84 μs    ±14.04%      664.23 μs      711.86 μs

Comparison: 
Part 1        2.09 K
Part 2        1.50 K - 1.40x slower +191.11 μs

Memory usage statistics:

Name           average  deviation         median         99th %
Part 1         0.59 MB     ±0.00%        0.59 MB        0.59 MB
Part 2         1.09 MB     ±0.02%        1.09 MB        1.09 MB

Comparison: 
Part 1         0.59 MB
Part 2         1.09 MB - 1.84x memory usage +0.50 MB

Reduction count statistics:

Name   Reduction count
Part 1         66.01 K
Part 2        131.63 K - 1.99x reduction count +65.62 K

**All measurements for reduction count were the same**
nil