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

Day 16 - Advent of Code 2023

lib/advent_of_code/2023/day-16.livemd

Day 16 - Advent of Code 2023

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

Links

Input

input = Kino.Input.textarea("Please paste your input file:")
input = input |> Kino.Input.read()
"\\....../...|..........|....../.../..-../.|...|......-.|..................................|.............../....\n|....-.........\\..........\\......./.....................................|.....-...........||..................\n.........-......|./...........\\.../-......./.|...\\.-.......................\\....\\.............................\n...............//...............\\.........................\\.......................\\....../........./.........\\\n..-........................-..........................................|............../..\\.........../.........\n..........\\....\\../...............|--....../..............-.............|...-.....\\........\\.........-...\\-...\n....|...|..|........................../..................-............./..\\-........../|.........|...........|\n.........-.....\\...................-.............|.............../....-....-....|............../....\\../-.....\n.....\\../\\......../..|........................|.......-........................../-...|.......................\n..............|/................|........|..-......|....|.|.......|.-....\\.../.............|.....-.......-....\n.-...|..................../..|......\\......|./..-.....\\../......................\\.|..--...................../.\n.............../..|...................-........./.................../....|.............-..........|.|..\\../...\n.|...........|-.........|/....\\.....\\...-...-.-.-......-...................................../.......\\-.......\n./................./.................\\..................../.-..................|.....................\\.....\\..\n\\........-...|......|........-...................\\.........................-..-........./....../.....\\........\n........-../..........|........................-..-/\\...\\........-.../.../...-......................./\\.....-.\n..//|\\.........../\\.......-...........\\....\\.....-./.|.............\\...../..\\.......||.......-................\n........./....\\.........\\-................/................................|-...\\/.........|......|...........\n...|........\\.../......\\....../......\\........./....|....../...../..|......../.-.........................\\....\n../......./....|................................................................................\\\\.......\\..|.\n.............-.\\.|..........|....................................................-..........|.................\n\\......|.............\\..-.....................................-.|...................................-|........\n.....................-......|\\......................|...-.\\......-.........-.........\\............//...-......\n.../.....\\..................|..-/....|.......\\......\\........./......./../............................\\../....\n....|....../.....-.............../........................../...................-...-.........................\n...................|....|.........-..../....|.......|../|...../....\\................../.............|.........\n........./..\\..-............................................\\.....\\......|...../\\.\\\\..|..|/................\\..\n.....|\\......|.............../............................/............................................../...-\n...............|./../............/.\\.\\..............-./...................-./...|./...........................\n-...|./.........../\\...-...........................|....-.....................-......\\..................-.....\n....................|..|/............-./.....-\\....|.............\\./.............../.|.................\\...|..\n..\\.|.-.\\.................\\...........-.........-..-.......................\\.....|\\......./.....-..........\\..\n.........|.........../............-...-...........-...-.......\\.|....................-......./...|............\n...|.......|.......................|...........\\................\\................\\............../..\\..........\n/........../..-..............-................../..........\\|..|..........-....\\...........|-.................\n.......\\.............-..................-\\......................\\...................-..........-..........|...\n../.....-\\...............-.....\\.............-....-\\/..........|.-............/...............\\....." <> ...

Solution

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

  def part1(input) do
    input
    |> parse()
    |> energize({{0, 0}, :right})
  end

  def part2(input) do
    grid = parse(input)

    max(
      max_x_energize(grid),
      max_y_energize(grid)
    )
  end

  defp max_x_energize(grid) do
    0..grid.max_x
    |> Enum.reduce(0, fn x, max_acc ->
      max_acc
      |> max(energize(grid, {{x, 0}, :down}))
      |> max(energize(grid, {{x, grid.max_y}, :up}))
    end)
  end

  defp max_y_energize(grid) do
    0..grid.max_y
    |> Enum.reduce(0, fn y, max_acc ->
      max_acc
      |> max(energize(grid, {{0, y}, :right}))
      |> max(energize(grid, {{grid.max_x, y}, :left}))
    end)
  end

  defp energize(grid, start) do
    grid
    |> advance_light([start], MapSet.new([start]))
    |> Enum.map(&amp;elem(&amp;1, 0))
    |> Enum.uniq()
    |> length()
  end

  defp advance_light(_grid, [], set), do: set

  defp advance_light(grid, lights, set) do
    new_lights =
      lights
      |> Enum.map(fn {xy, direction} ->
        next_xys(xy, direction, grid[xy])
      end)
      |> List.flatten()
      |> Enum.filter(fn {xy, _} -> Map.has_key?(grid, xy) end)
      |> Enum.reject(&amp;MapSet.member?(set, &amp;1))

    new_set = Enum.reduce(new_lights, set, &amp;MapSet.put(&amp;2, &amp;1))

    advance_light(grid, new_lights, new_set)
  end

  defp next_xys(xy, :up, c) when c in ~c".|", do: up(xy)
  defp next_xys(xy, :down, c) when c in ~c".|", do: down(xy)
  defp next_xys(xy, :left, c) when c in ~c".-", do: left(xy)
  defp next_xys(xy, :right, c) when c in ~c".-", do: right(xy)
  defp next_xys(xy, :up, ?/), do: right(xy)
  defp next_xys(xy, :up, ?\\), do: left(xy)
  defp next_xys(xy, :down, ?/), do: left(xy)
  defp next_xys(xy, :down, ?\\), do: right(xy)
  defp next_xys(xy, :left, ?/), do: down(xy)
  defp next_xys(xy, :left, ?\\), do: up(xy)
  defp next_xys(xy, :right, ?/), do: up(xy)
  defp next_xys(xy, :right, ?\\), do: down(xy)
  defp next_xys(xy, :up, ?-), do: [left(xy), right(xy)]
  defp next_xys(xy, :down, ?-), do: [left(xy), right(xy)]
  defp next_xys(xy, :left, ?|), do: [up(xy), down(xy)]
  defp next_xys(xy, :right, ?|), do: [up(xy), down(xy)]

  defp right({x, y}), do: {{x + 1, y}, :right}
  defp left({x, y}), do: {{x - 1, y}, :left}
  defp down({x, y}), do: {{x, y + 1}, :down}
  defp up({x, y}), do: {{x, y - 1}, :up}

  defmodule Input do
    def parse(input) do
      input
      |> String.splitter("\n", trim: true)
      |> Stream.with_index()
      |> Enum.reduce(%{}, &amp;parse_line/2)
      |> add_max_xy()
    end

    defp parse_line({line, row}, grid) do
      line
      |> String.to_charlist()
      |> Enum.with_index()
      |> Enum.reduce(grid, fn {char, col}, acc ->
        Map.put(acc, {col, row}, char)
      end)
    end

    defp add_max_xy(grid) do
      x = Map.keys(grid) |> Enum.map(&amp;elem(&amp;1, 0)) |> Enum.max()
      y = Map.keys(grid) |> Enum.map(&amp;elem(&amp;1, 1)) |> Enum.max()

      Map.merge(grid, %{max_x: x, max_y: y})
    end
  end
end
{:module, Day16, <<70, 79, 82, 49, 0, 0, 24, ...>>,
 {:module, Day16.Input, <<70, 79, 82, ...>>, {:add_max_xy, 1}}}

The light isn’t energizing enough tiles to produce lava; to debug the contraption, you need to start by analyzing the current situation. With the beam starting in the top-left heading right, how many tiles end up being energized?

Your puzzle answer was 6514.

Day16.part1(input)
6514

Find the initial beam configuration that energizes the largest number of tiles; how many tiles are energized in that configuration?

Your puzzle answer was 8089.

Day16.part2(input)
8089

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 Day16Test do
  use ExUnit.Case, async: false

  setup_all do
    [
      input:
        ".|...\\....\n|.-.\\.....\n.....|-...\n........|.\n..........\n.........\\\n..../.\\\\..\n.-.-/..|..\n.|....-|.\\\n..//.|...."
    ]
  end

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

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

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

Randomized with seed 296943
%{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 -> Day16.part1(input) end,
        "Part 2" => fn -> Day16.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        131.55      0.00760 s     ±5.98%      0.00751 s      0.00854 s
Part 2          0.77         1.30 s     ±0.43%         1.30 s         1.30 s

Comparison: 
Part 1        131.55
Part 2          0.77 - 170.72x slower +1.29 s

Memory usage statistics:

Name      Memory usage
Part 1       0.0135 GB
Part 2         2.19 GB - 161.55x memory usage +2.17 GB

**All measurements for memory usage were the same**

Reduction count statistics:

Name           average  deviation         median         99th %
Part 1          0.61 M     ±0.00%         0.61 M         0.61 M
Part 2        107.24 M     ±0.00%       107.24 M       107.24 M

Comparison: 
Part 1          0.61 M
Part 2        107.24 M - 176.11x reduction count +106.63 M
nil